【线程】线程的控制
本文重点:理解线程控制的接口
前言
内核中是没有很明确线程的概念的,只有轻量级进程的概念,不会提供直接给我们线程的系统调用,而会给我们提供轻量级进程的系统调用。我们用户是需要线程的接口的,在应用层,人们封装了轻量级进程的系统调用,为用户直接提供线程的接口,这个封装的就是线程库pthread库,这个是第三方库,几乎所有的Linux系统都会自带这个库,当我们进行编译链接时,要指定这个库
线程的创建
函数pthread_create

![]()
参数:
thread:输出型参数,返回线程ID
attr:设置线程的属性,attr为nullptr表示使用默认属性
start_routine:是个函数指针,线程启动后要执行的函数
arg:传给线程启动函数的参数,线程被创建成功,新线程回调线程函数的时候需要参数,这个参数是给线程函数传递的返回值:成功返回0,失败返回错误码
传统的一些函数是,成功返回0,失败返回-1,并且对全局变量errno赋值以指示错误。
pthreads函数出错时不会设置全局变量errno(而大部分其他线程函数会这样做)。而是将错误代码通过返回值返回
pthreads同样也提供了线程内的errno变量,以支持其它使用errno的代码。对于pthreads函数的错误,建议通过返回值判定,因为读取返回值要比读取线程内的errno变量的开销更小
线程的函数的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递对象
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>using namespace std;void* threadroutine(void * arg)
{const char* name=(const char*)arg;while(true){cout<<name<<",pid: "<<getpid()<<endl;sleep(2);}
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadroutine,(void*)"new thread");while(true){cout<<"main thread,pid: "<<getpid()<<endl;sleep(1);}return 0;
}

发现它们的pid是一样的
ps -aL 查看系统轻量级进程

LWP(light weight process):线程ID,这个线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
我们发现一个PID和LWP是一样的,那这个就是主线程,其他就是创建出来的线程
线程组内的第一个线程,在用户态被称为主线程(main thread),内核在创建第一个线程时,会将线程组的ID的值设置成第一个线程的线程ID,既主线程的进程描述符。所以线程组内存在一个线程ID等于进程ID,而该线程即为线程组的主线程
至于线程组其他线程的ID则有内核负责分配,其线程组ID总是和主线程的线程组ID一致,无论是主线程直接创建线程,还是创建出来的线程再次创建线程,都是这样
强调一点,线程和进程不一样,进程有父进程的概念,但在线程组里面,所有的线程都是对等关系
线程的终止
如果需要只终止某个线程而不终止整个进程,可以有三种方法:
1. 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit。
2. 线程可以调用pthread_ exit终止自己。
3. 一个线程可以调用pthread_ cancel终止同一进程中的另一个线程。
注意:以前的进程终止函数exit是用来终止进程的
函数pthread_exit

参数和线程函数的返回值类型是一样的都是void*,这就和return终止线程差不多了
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。
函数pthread_cancel
取消执行中的线程

线程的等待
在进程里有个进程等待,父进程等待子进程,是为了防止僵尸进程,回收子进程,防止内存泄漏
为什么需要线程等待?已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。创建新的线程不会复用刚才退出线程的地址空间。那么线程中也要有线程等待,如果没有,那么也会有类似于僵尸的状态
函数pthread_join


函数的作用:等待指定线程,可以获取这个线程函数的返回值void*(终止状态)
参数:
thread:线程ID
value_ptr:它指向一个指针,就是指向线程创建时,调用的线程函数的返回值void*
返回值:成功返回0;失败返回错误码

调用该函数的线程将阻塞等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:
1. 如果thread线程通过return返回,value_ ptr所指向的单元里存放的是thread线程函数的返回值。
2. 如果thread线程被别的线程调用pthread_ cancel异常终掉,value_ ptr所指向的单元里存放的是常数PTHREAD_CANCELED(一个宏常数,就是-1)。
3. 如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传pthread_exit的参数。
4. 如果对thread线程的终止状态不感兴趣,可以传nullptr给value_ ptr参数。
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<stdlib.h>using namespace std;void* threadroutine(void * arg)
{const char* name=(const char*)arg;int cnt=5;while(true){cout<<name<<",pid: "<<getpid()<<endl;sleep(1);cnt--;if(cnt==0) break;}return (void*)1;//我的当前平台,地址是64位,8字节,而int是4字节,不进行强转会导致混乱
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadroutine,(void*)"new thread");void * retval;pthread_join(tid,&retval);// main thread等待的时候,默认是阻塞等待的!cout << "main thread quit ..., ret: " << (long long int)retval << endl;return 0;
}

为什么我们在这里join的时候不考虑异常呢??因为做不到,线程异常了整个进程都会挂掉。线程的健壮性低
线程的分离
上面的线程等待,只能阻塞等待,那如果不想阻塞怎么办,并且不关心线程的返回值,那就让线程分离,线程分离之后,主线程就不管了,主线程就干自己的事了,自己的资源自己释放回收了,虽然线程分离了,但是还是属于这个进程的
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离
joinable和分离是冲突的,一个线程不能既是joinable又是分离的,也就是说,线程分离了,主线程就不用等待回收线程了

#include<iostream>
#include<cstring>
#include<unistd.h>
#include<pthread.h>
void* PthreadRoutine(void* args)
{int i=0;pthread_detach(pthread_self());while(i<3){cout<<"child thread ,pid: "<<getpid()<<endl;i++;sleep(1);}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,PthreadRoutine,nullptr);sleep(1);//确保分离成功int n=pthread_join(tid,nullptr);printf("n = %d, who = 0x%x, why: %s\n", n, tid, strerror(n));return 0;}

线程ID及进程地址空间布局
pthread_ create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_ create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。
线程库NPTL提供了pthread_ self函数,可以获得线程自身的ID
函数pthread_self

函数clone

这个函数clone是系统调用,用来创建轻量级进程的,前言说的pthread库的底层就是用这个函数封装的

使用线程库,是要加载到内存的,它是动态库,所以会在地址空间的共享区。
线程的概念是库给我们维护的,线程库注定要维护多个线程,线程库要管理这些线程,先描述再组织,每个线程都会有一个库级别的结构体tcb,这个结构体的起始地址就是线程的id

除了主线程外,所有的其他线程的独立栈,都在共享区,具体来讲,是在pthread库中,tid指向用户的tcb。全局变量和堆区都是线程共享的,栈不是,每个线程都有自己的独立栈
目前,我们的原生线程,pthread库,原生线程库,C++11 语言本身也已经支持多线程了 vs 原生线程库。其实C++的多线程底层封装的就是Linux系统下的原生线程库,为什么C++移值性高,就是因为C++的代码在不同平台上都可以跑,在Linux下跑多线程底层就是Linux的原生线程库,在Windows跑,底层就是Windows的原生线程库,C++语音在底层设计时,不同的系统有不同的设计,但是在上层看来都一样
#include <iostream>
#include <thread>using namespace std;void threadrun()
{while(true){cout << "I am a new thead for C++" << endl;sleep(1);}
}int main()
{thread t1(threadrun);t1.join();return 0;
}
多线程的创建
1.创建多线程
#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<pthread.h>using namespace std;#define NUM 4class ThreadData
{
public:ThreadData(int number){_threadname="thread-"+to_string(number);}
public:string _threadname;
};string toHex(pthread_t tid)
{char buf[128];snprintf(buf,sizeof(buf),"0x%x",tid);return buf;}
void* ThreadRoutine(void* args)
{ThreadData* td=static_cast<ThreadData*>(args);string tid=toHex(pthread_self());pid_t pid=getpid();int i=0;while(i<3){cout<<td->_threadname<<" pid: "<<pid<<" tid: "<<tid<<endl;i++;sleep(1);}delete td;return nullptr;
}
int main()
{vector<pthread_t> tids;//创建多线程for(int i=1;i<=NUM;i++){pthread_t tid;ThreadData* td=new ThreadData(i);pthread_create(&tid,nullptr,ThreadRoutine,td);tids.push_back(tid);sleep(1);}//等待多线程for(auto tid:tids){pthread_join(tid,nullptr);}cout<<"main thread wait success"<<endl;return 0;
}

上面代码在主线程开辟的堆空间传递给了子线程,子线程还可以利用,说明堆空间是被所有线程共享的
2.验证全局变量共享
在上面代码上加个全局变量g_val,在子线程打印他的地址
#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<pthread.h>using namespace std;#define NUM 4
int g_val=0;//定义一个全局class ThreadData
{
public:ThreadData(int number){_threadname="thread-"+to_string(number);}
public:string _threadname;
};string toHex(pthread_t tid)
{char buf[128];snprintf(buf,sizeof(buf),"0x%x",tid);return buf;}
void* ThreadRoutine(void* args)
{ThreadData* td=static_cast<ThreadData*>(args);string tid=toHex(pthread_self());pid_t pid=getpid();int i=0;while(i<3){cout<<td->_threadname<<" pid: "<<pid<<" tid: "<<tid<<",g_val: "<<g_val<<",&g_val: "<<&g_val<<endl;i++;sleep(1);}delete td;return nullptr;
}
int main()
{vector<pthread_t> tids;//创建多线程for(int i=1;i<=NUM;i++){pthread_t tid;ThreadData* td=new ThreadData(i);pthread_create(&tid,nullptr,ThreadRoutine,td);tids.push_back(tid);sleep(1);}//等待多线程for(auto tid:tids){pthread_join(tid,nullptr);}cout<<"main thread wait success"<<endl;return 0;
}

发现这个全局变量的地址都是一样的,说明全局变量被所有线程共享
3.线程的局部存储
如果线程不想共享全局变量,想要自己私有的全局变量,那么在全局变量加__thread修饰(两个下划线),这个是个编译选项,只能用来定义内置类型,不能修饰自定义类型,那么这个全局变量就不在全局区了,就在线程的栈结构的局部存储中,这个是线程级别的全局变量,也就是在某个线程里面时全局的


地址不一样,并且这个地址挺大的,说明在中间的堆栈之间
4.验证线程具有独立栈结构
在每个线程里面加个变量test_i,打印他的地址
#include<iostream>
#include<string>
#include<vector>
#include<unistd.h>
#include<pthread.h>using namespace std;#define NUM 4class ThreadData
{
public:ThreadData(int number){_threadname="thread-"+to_string(number);}
public:string _threadname;
};string toHex(pthread_t tid)
{char buf[128];snprintf(buf,sizeof(buf),"0x%x",tid);return buf;}
void* ThreadRoutine(void* args)
{ThreadData* td=static_cast<ThreadData*>(args);string tid=toHex(pthread_self());pid_t pid=getpid();int i=0;int test_i=0;while(i<3){cout<<td->_threadname<<" pid: "<<pid<<" tid: "<<tid<<",test_i: "<<test_i<<",&test_i: "<<&test_i<<endl;;i++;sleep(1);}delete td;return nullptr;
}
int main()
{vector<pthread_t> tids;//创建多线程for(int i=1;i<=NUM;i++){pthread_t tid;ThreadData* td=new ThreadData(i);pthread_create(&tid,nullptr,ThreadRoutine,td);tids.push_back(tid);sleep(1);}//等待多线程for(auto tid:tids){pthread_join(tid,nullptr);}cout<<"main thread wait success"<<endl;return 0;
}

发现test_i的地址都不一样,说明它们有独立的栈结构,并不是私有的栈结构,主线程想访问也可以访问的,线程中没有秘密,如果要访问还是可以访问的
相关文章:
【线程】线程的控制
本文重点:理解线程控制的接口 前言 内核中是没有很明确线程的概念的,只有轻量级进程的概念,不会提供直接给我们线程的系统调用,而会给我们提供轻量级进程的系统调用。我们用户是需要线程的接口的,在应用层࿰…...
掌握 Spring:从新手到高手的常见问题汇总
一提起Spring,总感觉有太多知识,无法详尽,有些基础理解就先不说了,相信大家都已经用过Spring了 下面简单针对常见Spring面试题做些回答 核心特性 IOC容器spring事件资源管理国际化校验数据绑定类型转换spirng表达式面向切面编程……...
机器学习——Bagging
Bagging: 方法:集成n个base learner模型,每个模型都对原始数据集进行有放回的随机采样获得随机数据集,然后并行训练。 回归问题:n个base模型进行预测,将得到的预测值取平均得到最终结果。 分类问题…...
日志体系结构与框架:历史、实现与如何在 Spring Cloud 中使用日志体系
文章目录 1. 引言2. 日志体系结构3. 日志框架的发展历程日志框架特点对比 4. 日志记录器的使用与管理使用 SLF4J 和 Logback 的日志记录示例 5. Spring Cloud 中的日志使用5.1 日志框架集成5.2 分布式追踪:Spring Cloud Sleuth 和 Zipkin添加 Sleuth 和 Zipkin 依赖…...
图文深入理解SQL语句的执行过程
List item 本文将深入介绍SQL语句的执行过程。 一.在RDBMS(关系型DB)中,看似很简单的一条已写入DB内存的SQL语句执行过程却非常复杂,也就是说,你执行了一条诸如select count(*) where id 001 from table_name的非常简…...
ubuntu安装StarQuant
安装boost 下面展示一些 内联代码片。 sudo apt install libboost-all-dev -y安装libmongoc-1.0 链接: link // An highlighted block sudo apt install libmongoc-1.0-0 sudo apt install libbson-1.0 sudo apt install cmake libssl-dev libsasl2-dev编译源码 $ git clone…...
学习篇 | Jupyter 使用(notebook hub)
1. JupyterHub 1.1 快速尝试 jupyterhub -f/path/jupyter_config.py --no-ssl1.2 长期后台运行 bash -c "nohup jupyterhub -f/path/jupyter_config.py --no-ssl" > ~/jupyterhub.log 2>&1 &1.3 帮助 jupyterhub --help2. Jupyter Notebook 2.1 快…...
【裸机装机系列】8.kali(ubuntu)-虚拟内存swap交换分区扩展
推荐阅读: 1.kali(ubuntu)-为什么弃用ubuntu,而选择基于debian的kali操作系统 linux swap交换分区,相当于win系统虚拟内存的概念。当linux系统的物理内存不够用的时候,就需要将物理内存中的一部分空间释放出来,以供当前…...
异步请求的方法以及原理
异步请求是指在发送请求后,不会阻塞程序的执行,而是继续执行后续的代码,等待请求返回后再执行相应的回调函数。常见的异步请求方法包括使用XMLHttpRequest对象(XHR)和fetch API。 异步请求的方法 1. XMLHttpRequest (X…...
SpringCloud入门(六)Nacos注册中心(下)
一、Nacos环境隔离 Nacos提供了namespace来实现环境隔离功能。 nacos中可以有多个namespace。namespace下可以有group、service等。不同namespace之间相互隔离,例如不同namespace的服务互相不可见。 使用Nacos Namespace 环境隔离 步骤: 1.在Nacos控制…...
【RDMA】mlxlink检查和调试连接状态及相关问题--驱动工具
简介 mlxlink工具用于检查和调试连接状态及相关问题。该工具可以用于不同的链路和电缆(包括被动、电动、收发器和背板)。 属于mft工具套件的一个工具,固件工具 Firmware Tools (MFT):https://blog.csdn.net/bandaoyu/article/details/14242…...
QT For Android开发-打开PPT文件
一、前言 需求: Qt开发Android程序过程中,点击按钮就打开一个PPT文件。 Qt在Windows上要打开PPT文件或者其他文件很容易。可以使用QDesktopServices打开文件,非常方便。QDesktopServices提供了静态接口调用系统级别的功能。 这里用的QDesk…...
SpringBoot教程(三十) | SpringBoot集成Shiro权限框架
SpringBoot教程(三十) | SpringBoot集成Shiro权限框架 一、 什么是Shiro二、Shiro 组件核心组件其他组件 三、流程说明shiro的运行流程 四、SpringBoot 集成 Shiro (shiro-spring-boot-web-starter方式)1. 添加 Shiro 相关 maven2…...
[ffmpeg] 视频格式转换
本文主要梳理 ffmpeg 中的视频格式转换。由于上屏的数据是 rgba,编码使用的是 yuv数据,所以经常会使用到视频格式的转换。 除了使用 ffmpeg进行转换,还可以通过 libyuv 和 directX 写 shader 进行转换。 之前看到文章说 libyuv 之前是 ffmpeg…...
git-repo系列教程(3) git-repo https证书认证问题
文章目录 问题描述解决步骤1.下载证书2.测试证书是否正常3.设置环境变量 总结 问题描述 在使用git repo 同步仓库时,发现不能同步,出现如下提示错误: % Total % Received % Xferd Average Speed Time Time Time CurrentDload Upload Total Spent Left …...
中序遍历二叉树全过程图解
文章目录 中序遍历图解总结拓展:回归与回溯 中序遍历图解 首先看下中序遍历的代码,其接受一个根结点root作为参数,判断根节点是否为nil,不为nil则先递归遍历左子树。 func traversal(root *TreeNode,res *[]int) {if root nil …...
设计模式 组合模式(Composite Pattern)
组合模式简绍 组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端可以用一致的方式处理单个对象和组合对象。这样,可以在不知道对象具体类型的条…...
在vue中嵌入vitepress,基于markdown文件生成静态网页从而嵌入社团周报系统的一些想法和思路
什么是vitepress vitepress是一种将markdown文件渲染成静态网页的技术 其使用仅需几行命令即可 //在根目录安装vitepress npm add -D vitepress //初始化vitepress,添加相关配置文件,选择主题,描述,框架等 npx vitepress init //…...
神经网络面试题目
1. 批规范化(Batch Normalization)的好处都有啥?、 A. 让每一层的输入的范围都大致固定 B. 它将权重的归一化平均值和标准差 C. 它是一种非常有效的反向传播(BP)方法 D. 这些均不是 正确答案是:A 解析: batch normalization 就…...
C语言题目之单身狗2
文章目录 一、题目二、思路三、代码实现 提示:以下是本篇文章正文内容,下面案例可供参考 一、题目 二、思路 第一步 在c语言题目之打印单身狗我们已经讲解了在一组数据中出现一个单身狗的情况,而本道题是出现两个单身狗的情况。根据一个数…...
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?
Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以? 在 Golang 的面试中,map 类型的使用是一个常见的考点,其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
