当前位置: 首页 > 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 #查看当前目录下所有文件 …...

安宝特方案丨XRSOP人员作业标准化管理平台:AR智慧点检验收套件

在选煤厂、化工厂、钢铁厂等过程生产型企业&#xff0c;其生产设备的运行效率和非计划停机对工业制造效益有较大影响。 随着企业自动化和智能化建设的推进&#xff0c;需提前预防假检、错检、漏检&#xff0c;推动智慧生产运维系统数据的流动和现场赋能应用。同时&#xff0c;…...

ESP32读取DHT11温湿度数据

芯片&#xff1a;ESP32 环境&#xff1a;Arduino 一、安装DHT11传感器库 红框的库&#xff0c;别安装错了 二、代码 注意&#xff0c;DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

sqlserver 根据指定字符 解析拼接字符串

DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的

修改bug思路&#xff1a; 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑&#xff1a;async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...

解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist

现象&#xff1a; android studio报错&#xff1a; [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决&#xff1a; 不要动CMakeLists.…...

GraphQL 实战篇:Apollo Client 配置与缓存

GraphQL 实战篇&#xff1a;Apollo Client 配置与缓存 上一篇&#xff1a;GraphQL 入门篇&#xff1a;基础查询语法 依旧和上一篇的笔记一样&#xff0c;主实操&#xff0c;没啥过多的细节讲解&#xff0c;代码具体在&#xff1a; https://github.com/GoldenaArcher/graphql…...