当前位置: 首页 > news >正文

Linux——线程控制

目录

前言

一、线程创建

1.创建线程

2.线程传递结构体 

3.创建多线程 

4.收到信号的线程

二、线程终止

三、线程等待

四、线程分离

五、取消线程

六、线程库管理的原理

七、站在语言角度理解pthread库

八、线程的局部存储


前言

前面我们学习了线程概念和线程创建,今天我们学习线程控制,如何操控一个线程完成任务,同时能取消线程、等待线程,分离线程。

一、线程创建

1.创建线程

功能:创建一个新的线程

  • 参数 thread:返回线程ID
  • attr:设置线程的属性,attr为NULL表示使用默认属性
  • start_routine:是个函数地址,线程启动后要执行的函数
  • arg:传给线程启动函数的参数
  • 返回值:成功返回0;失败返回错误码
#include<iostream>
#include<unistd.h>
#include<pthread.h>
using namespace std;void* TreadToutine(void *arg)
{const char* threadname = (const char*) arg;while(1){cout<<"我是一个新线程"<<threadname<<endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid,NULL,TreadToutine, (void*)"thread 1");//主线程while(1){cout<<"我是主线程"<<endl;sleep(1);}return 0;
}

linux没有真正的线程概念,他的线程是复用的进程代码,只是做了一些区分。线程客观的可以叫做轻量级进程。因此Linux只会提供轻量级进程创建的函数调用,不会直接提供线程创建的接口。因此我们使用pthread原生线程库,编译时需要手动链接库文件(-lpthread)。

这样编译后就可以运行了。

从上面代码可以看出,给线程传递的信息可以是char*,由于pthread_create函数的最后一个参数为void*,同时线程去运行的函数参数也是void*,因此我们任意类型都可以传递过去,进行一下强转即可。

2.线程传递结构体 

比如现在我想传递很多内容过去,叫线程帮我们处理 

如下,我们传递了结构体 

线程成功收到结构体,并做出了处理。

3.创建多线程 

 创建多线程也很简单,只需要循环创建即可。

4.收到信号的线程

如果进程创建的线程有一个发生了异常,收到了信号,会导致整个进程都被终止,因为线程是进程创建出来的,发送信号是发给了进程,进程如果退出,那么该进程所有的资源也都得被回收。而线程本身就是进程资源的一部分。

二、线程终止

我们知道线程去执行的函数返回类型为void*,当线程执行结束,return时,线程就自动终止了

如果我们返回时调用exit()函数 ,那么整个进程都会被终止

同时,pthread.h库还给我们提供了 pthread_exit() 接口,我们使用该接口也可以终止线程。

pthread_exit()

作用:终止一个运行的线程

参数retval:返回void*的全局变量

注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

代码如下,两种方法都可以退出 

 运行结果如下,线程被退出,不再打印消息。

三、线程等待

线程退出默认要被等待,如果不等待,就会发生类似于僵尸进程的问题。因此我们需要用pthread_join()函数进行等待

pthread_join()

功能:等待线程结束

  • 参数1::thread:线程ID
  • 参数2:value_ptr:它指向一个指针,后者指向线程的返回值(void**指向的线程返回值void*)
  • 返回值:成功返回0;失败返回错误码

代码如下,让子线程程循环5次后退出并传参常量字符串,主线程去join等待,并将等待的结果输出。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
using namespace std;class Add
{
public:Add(string name, int a, int b): _name(name), _a(a), _b(b){}public:string _name;int _a;int _b;
};void *TreadToutine(void *arg)
{Add *a1 = (Add *)arg;int cnt = 5;while (cnt--){cout << "我是一个新线程: " << a1->_name << ",计算结果为" << a1->_a + a1->_b << endl;sleep(1);}//return nullptr;pthread_exit((void*)"pthread-1 退出"); //常量区
}int main()
{pthread_t tid;Add *td = new Add("thread-1", 10, 20);pthread_create(&tid, NULL, TreadToutine, td);// 主线程cout << "我是主线程,子线程的tid: "<< tid << endl;void* msg = nullptr;pthread_join(tid,&msg);cout<<"等待成功,子线程退出信息: "<<(char*)msg <<endl;sleep(1);return 0;
}

等待成功,同时输出了消息。注意等待是阻塞式等待,子线程退出后才会执行后续代码。

四、线程分离

我们知道,线程是需要被等待的,不然会发生类似于僵尸进程的现象,那么如果我想让线程一直去运行,比如说一直帮我播放音乐,那么主线程就会一直等待,不可能执行后面的代码。

在这种情况下,我们可以让线程分离,也就是主线程不再关心创建的子线程的死活,他要运行就运行,不运行了操作系统会回收。不过一般建议主线程最后再退出

可以使用pthread_detach()函数进行线程的分离。

pthread_detach()

作用:分离线程

分离线程很简单,直接调用pthread_detach()就可以,我们不过多展示,下面代码是先分离线程,再等待线程看看会发生什么。 

发现等待线程的返回值为22,不是0证明等待失败,22的意思是该线程不需要等待。

这是我们是在主线程进行分离的,子线程也可以被分离, 由于子线程默认看不到自己的tid,因此可以调用pthread_self()函数获取自己的tid。

pthread_self

作用:让线程获取自己的tid

如下是子线程选择分离。 

小总结:

  • 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。
  • 如果不关心线程的返回值,join是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。 

五、取消线程

主线程可以取消线程,也就是让子线程退出,可以调用pthread_cancel()函数进行终止线程。

pthread_cancel()

功能:向线程发送取消请求

  • 参数1:thread,线程ID
  • 返回值:成功返回0;失败返回错误码

代码如下,先取消进程,再等待线程,同时查看线程退出码

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
using namespace std;void *TreadToutine(void *arg)
{while(1){cout << "我是一个新线程" << endl;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, NULL, TreadToutine, (void *)"pthread-1");sleep(3);// 取消线程int n = pthread_cancel(tid);cout << "线程取消成功,n: " << n << endl;// 等待线程void *ret = nullptr;n = pthread_join(tid, &ret);cout << "等待线程返回值n: " << n << ",线程返回值: " << (int64_t)ret << endl;return 0;
}

运行看到,线程返回值为0,取消成功,等待返回值为0,等待成功。我们看到线程没有阻塞在等待函数这里,而是直接往后运行,同时进程返回为-1。

这是因为如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数 PTHREAD_ CANCELED

而如果线程先被脱离,再取消,结果怎么样呢?

发现也是能被取消的,但是线程等待是22(等待失败)。因为系统直接回收了。 

小总结:

1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。

2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED。

3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。

4. 如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。 

六、线程库管理的原理

我们对线程的操作一直要使用tid,那么tid里面的内容到底是什么呢?

其实他是一个地址,我们转成16进程来看一下。

确实是很像是地址,但这跟LWP(Light Weight Process)也不一样啊。该如何理解呢? 

  • 首先,我们知道pthread. h不是操作系统的接口,而是原生线程库。那么用户创建的线程,操作系统无法管理,则需要线程库来进行管理。他从系统中获取轻量级进程相关属性,从用户中也获取一些属性,这样就先描述起来了,再通过数据结构将线程组织起来,就将线程管理好了。
  • 我们也知道,线程要有独立属性,独立的主要有硬件上下文和栈空间,其中硬件上下文跟操作系统有关,而栈空间则是要从用户中来。栈不是只有一个吗?为什么每一个线程都有自己的栈空间呢?这其实是操作系统帮我们处理了的,操作系统会在堆区创建空间,来充当线程独立的栈。pthread库会获取到栈空间,并将他管理维护好,而默认地址空间中的栈,由主线程使用。

那么线程库如何管理呢,在哪管理呢? 

  • 在进程地址空间中,mmap(共享区)加载了动态库,其中我们使用的pthread库就在该区域,他会管理好每一份线程,每一份线程都在其中有自己的属性集。
  • struct pthread里存在很多线程属性,线程局部存储,还有线程栈,这个栈指向的是堆空间的区域,每当有新线程被创建,都会在后面继续创建这种数据结构。就这样将多个线程统一的描述组织起来了,可以进行管理了。因此我们调用pthread相关函数,相当于对该空间进行访问、处理。

那么现在,我们也可以理解 pthread_t tid 是什么了,他不就是每一个线程在进程地址空间的起始地址嘛,我们pthread_create 对tid进行写入,因为需要创建对应的数据结构,找到起始地址,然后返回,后续用户要继续对线程进行控制,等待啊,终止啊,分离啊,取消啊。都需要传入tid,也就是能找到在进程地址空间的位置后,才可以处理。

七、站在语言角度理解pthread库

我们之前学的pthread库,是Linux提供的原生线程库,在语言层面,比如C++/JAVA\PYTHON,他们也会提供给我们线程库。

我们写了一份代码,使用的是C++提供的线程库 thread

 #include<iostream>#include<unistd.h>#include<thread>using namespace std;void myrun()
{while(1){cout<<"我是一个新线程"<<endl;sleep(1);}
}int main(){thread t(myrun);t.join();}

编译后运行,发现说多线程操作被禁止了,这是因为我们没有链接pthread库。

c++提供的线程库封装了pthread.h。因此我们编译时仍然需要链接 pthread库。

到现在,我们可以知道,语言上也许线程库的使用不一定相同,但是他们底层都是用的linux原生线程库。

在Linux下做了封装,那么这段代码我们可以在Linux中运行。

如果thread头文件在Windows下,封装了Windows线程的操作,那么也可以在Windows下运行。这大大提高了文件的可移植性。

八、线程的局部存储

我们定义一个全局变量,创建线程,让新线程对全局变量做++,观察新线程和主线程全局变量是否发生变化。

#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;int g_val = 100;void *TreadToutine(void *arg)
{while (1){cout << "我是一个新线程,g_val: " << g_val << ",&g_val: " << &g_val << endl;g_val++;sleep(1);}return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid, NULL, TreadToutine, (void *)"Thread 1");while (1){cout << "我是一个主线程,g_val: " << g_val << ",&g_val: " << &g_val << endl;sleep(1);}pthread_join(tid,nullptr);
}

 我们可以看到,全局变量值一样,地址也一样,我们现在知道全局变量是被所有进程共享的。

如果我们给全局变量前添加上__thread,GCC/G++编译器提供的一个扩展,用于声明线程局部存储变量。

现在运行,主线程和新线程g_val不一样,地址也不一样。

因为我们添加的__thread 会在G++编译时,给每个线程的局部存储空间里将变量拷贝进程,私有一份,于是每个线程自己管理自己的那一份资源。不再与外部共享。 

 只是__thread只能修饰内置类型,如string这种自定义类型无法处理。

相关文章:

Linux——线程控制

目录 前言 一、线程创建 1.创建线程 2.线程传递结构体 3.创建多线程 4.收到信号的线程 二、线程终止 三、线程等待 四、线程分离 五、取消线程 六、线程库管理的原理 七、站在语言角度理解pthread库 八、线程的局部存储 前言 前面我们学习了线程概念和线程创建&…...

【Leetcode 347】,前k个高频元素,小根堆的调整

参考题解 题目&#xff1a;给定一个数组&#xff0c;输出 前k个高频元素。 思路&#xff1a; 遍历数组&#xff0c;建立小根堆&#xff08;小根堆的元素是元组&#xff08;num,freq&#xff09;&#xff0c;排序规则是每个元素的频率&#xff09;。 下面使用数组‘heap’&…...

【图论】【分类讨论】LeetCode3017按距离统计房屋对数目

本文涉及的知识点 图论 分类讨论 本题同解 【差分数组】【图论】【分类讨论】【整除以2】3017按距离统计房屋对数目 LeetCode3017按距离统计房屋对数目 给你三个 正整数 n 、x 和 y 。 在城市中&#xff0c;存在编号从 1 到 n 的房屋&#xff0c;由 n 条街道相连。对所有 …...

浅谈Yum 安装和 源码安装

浅谈Yum 安装和 源码安装 本文所叙述的Linux系统是基于RedHat发行版的CentOS7 yum安装 1. 前言 我们知道在Windows上下载的安装包后缀是 .exe &#xff0c;与之对应的 在 Linux下的安装包的后缀是 .rpm rpm (Red Hat Package Manager) 是红帽软件包管理器 我们在Windows电脑…...

JavaEE初阶Day 3:多线程(1)

目录 Day 3&#xff1a;多线程&#xff08;1&#xff09;1. 线程1.1 引入线程的原因1.2 线程的定义1.3 为何线程更轻量1.4 问题 2. 多线程代码2.1 继承Thread重写run2.2 通过实现Runnable接口创建线程2.3 针对2.1的变形使用匿名内部类2.4 针对Runnable创建匿名内部类2.5 使用la…...

gutil140.dll是什么?gutil140.dll无法继续执行的解决方法

gutil140.dll文件是一个动态链接库&#xff08;DLL&#xff09;文件&#xff0c;通常与Microsoft Visual Studio 2015相关联。 gutil140.dll是开发过程中使用的工具函数集合&#xff0c;它辅助开发人员执行常见的编程任务&#xff0c;如文件操作、内存分配和字符串处理等。这个…...

在CentOS 7上安装Python 3.7.7

文章目录 一、实战步骤1. 安装编译工具2. 下载Python 3.7.7安装包3. 上传Python 3.7.7安装包4. 解压缩安装包5. 切换目录并编译安装6. 配置Python环境变量7. 使配置生效8. 验证安装是否成功 二、实战总结 一、实战步骤 1. 安装编译工具 在终端中执行以下命令 yum -y groupin…...

基于SpringBoot Vue宠物领养系统

一、&#x1f4dd;功能介绍 基于SpringBoot Vue宠物领养系统 角色&#xff1a;管理员、用户 当游客打开系统的网址后&#xff0c;首先看到的就是首页界面。在这里&#xff0c;游客能够看到宠物领养救助平台的导航条显示首页、宠物招领、宠物认领、 宠物论坛、宠物资讯、后台管…...

ip命令

ip a 也是ip addr简写 [rootlocalhost ~]# ip a 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host lovalid_lft forever preferred_lft…...

【Kaggle】练习赛《鲍鱼年龄预测》(上)

前言 上一篇文章&#xff0c;讲解了《肥胖风险的多类别预测》机器学习方面的文章&#xff0c;主要是多分类算法的运用&#xff0c;本文是一个回归的算法&#xff0c;本期是2024年4月份的题目《Regression with an Abalone Dataset》即《鲍鱼年龄预测》&#xff0c;在此分享高手…...

Ruby 之交租阶段信息生成

题目 我看了一下&#xff0c;这个题目应该不是什么机密&#xff0c;所以先放上来了。大概意思是根据合同信息生成交租阶段信息。 解答 要求是要使用 Ruby 生成交租阶段信息&#xff0c;由于时间比较仓促&#xff0c;变量名那些就用得随意了些。要点主要有下面这些&#xff1a…...

RUST语言值所有权之内存复制与移动

1.RUST中每个值都有一个所有者,每次只能有一个所有者 String::from函数会为字符串hello分配一块内存 内存示例如下: 在内存分配前调用s1正常输出 在分配s1给s2后调用报错 因为s1分配给s2后,s1的指向自动失效 s1被move到s2 s1自动释放 字符串克隆使用...

【Django学习笔记(三)】BootStrap介绍

BootStrap介绍 前言正文1、BootStrap 快速了解2、初识BootStrap2.1 下载地址2.2 创建目录2.3 引入BootStrap2.4 使用BootStrap 3、BootStrap 组件&样式3.1 导航条3.2 栅格系统3.3 container3.3.1 container3.3.2 container-fluid 3.4 面板3.5 媒体对象3.6 分页3.7 图标3.7.…...

ClickHouse开发相关(UDAF)

ClickHouse开发相关(UDAF) ClickHouse介绍 ClickHouse是一个开源、高性能的列式 OLAP 数据库管理系统,用于使用 SQL 进行实时分析。 为什么需要ClickHouse UDAF? ClickHouse中已存在了许多聚合函数,绝大多数情况下已经覆盖我们的需求,但是有时候我们仍然需要自定义函数…...

MySql并发事务问题

事务 事务概念&#xff1a; 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 事务的特性&#xff1a;ACID&#xff1a; 小…...

Windows下Docker创建Mysql5.7

安装 下载镜像&#xff0c;注意&#xff0c;要带版本号 docker pull mysql:5.7 等下载完成执行命令&#xff1a; 错误命令1&#xff0c;直接Windows下路径&#xff1a; docker run --name mysql57 --restartalways -p 3306:3306 -v F:/mysqldata/data57/log:/var/log/mysql…...

Redis(性能管理、主从复制、哨兵模式)概述及部署

目录 一、性能管理 1、查看Redis内存使用 2、内存碎片率 3、跟踪内存碎片率 4、内存使用率 5、内回收key 二、Redis集群有三种模式 三、Redis主从复制 1、主从复制的概念 2、主从复制的作用 3、主从复制的流程 4、搭建Redis主从复制 1.环境准备 2.安装Redis&#…...

LabVIEW挖坑指南

一、挖坑指南 1.1、输出变量放在条件框内 错误写法&#xff1a; 现象&#xff1a;如果没进入对应的分支&#xff0c;输出为默认值 正常写法&#xff1a; 让每个分支输出的值都在预料之内。 1.2、统计耗时不准 错误写法 现象&#xff1a;统计出来的耗时是2000ms 正常写法&a…...

docker容器环境安装记录(MAC M1)(完善中)

0、背景 在MAC M1中搭建商城项目环境时&#xff0c;采用docker统一管理开发工具&#xff0c;期间碰到了许多环境安装问题&#xff0c;做个总结。 1、安装redis 在宿主机新建redis.conf文件运行创建容器命令&#xff0c;进行容器创建、端口映射、文件挂载、以指定配置文件启动…...

Linux 常用命令(持续更新中...)

1. ls 查看文件列表命令 语法&#xff1a; ls [-a -l -h] [Linux路径] -a -l -h 是可选的选项 &#xff08;-h需配合-l命令一起使用&#xff09;Linux路径是此命令可选的参数 ls #查看当前目录所有非隐藏文件(平铺方式显示) ls -a #查看当前目录下所有文件 …...

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢

随着互联网技术的飞速发展&#xff0c;消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁&#xff0c;不仅优化了客户体验&#xff0c;还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用&#xff0c;并…...

数据库分批入库

今天在工作中&#xff0c;遇到一个问题&#xff0c;就是分批查询的时候&#xff0c;由于批次过大导致出现了一些问题&#xff0c;一下是问题描述和解决方案&#xff1a; 示例&#xff1a; // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...

RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程

本文较长&#xff0c;建议点赞收藏&#xff0c;以免遗失。更多AI大模型应用开发学习视频及资料&#xff0c;尽在聚客AI学院。 本文全面剖析RNN核心原理&#xff0c;深入讲解梯度消失/爆炸问题&#xff0c;并通过LSTM/GRU结构实现解决方案&#xff0c;提供时间序列预测和文本生成…...

Fabric V2.5 通用溯源系统——增加图片上传与下载功能

fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...

基于Java+VUE+MariaDB实现(Web)仿小米商城

仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意&#xff1a;运行前…...

Python 实现 Web 静态服务器(HTTP 协议)

目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1&#xff09;下载安装包2&#xff09;配置环境变量3&#xff09;安装镜像4&#xff09;node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1&#xff09;使用 http-server2&#xff09;详解 …...

Golang——7、包与接口详解

包与接口详解 1、Golang包详解1.1、Golang中包的定义和介绍1.2、Golang包管理工具go mod1.3、Golang中自定义包1.4、Golang中使用第三包1.5、init函数 2、接口详解2.1、接口的定义2.2、空接口2.3、类型断言2.4、结构体值接收者和指针接收者实现接口的区别2.5、一个结构体实现多…...