news 2026/1/11 15:11:39

【Linux】线程深度指南:从等待、分离到 C++ 多线程实战,一文搞懂线程 ID 与进程空间(4)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Linux】线程深度指南:从等待、分离到 C++ 多线程实战,一文搞懂线程 ID 与进程空间(4)


一、线程等待

#include <iostream> #include <pthread.h> #include <cstdio> #include <stdlib.h> #include <ctime> #include <unistd.h> #include "Task.hpp" #include <vector> void* Routine(void* args) { std::string name = static_cast<const char*>(args); while(true) { std::cout << "new thread" << name << std::endl; sleep(5); break; } return (void*)10; } //线程等待 int main() { pthread_t tid; pthread_create(&tid,nullptr,Routine,(void*)"thread_1"); //等待线程退出,否则会出现类似于进程僵尸问题 void* retval = nullptr; int n = pthread_join(tid,&retval); if(n == 0)//等待成功 { std::cout << "join success: " << (long long)retval << std::endl; } return 0; }

注意:使用 ps -aL 是查不到线程退出时的僵尸状态的,因为 ps -aL 查的是系统级的轻量级进程,在内核中已经把这个线程释放了,但是线程管理的相关消息在 pthread 库内部中还存在。

问题:pthread_join 获取到的退出消息,为什么没有退出信号?为什么没有进行异常分析

答:新线程出异常,进程全部退出,线程没机会 join 成功,所以线程不需要关系异常。

注意:Routine 的返回值不一定是整数,他可以是一个字符串、类对象等。

#include <iostream> #include <pthread.h> #include <cstdio> #include <stdlib.h> #include <ctime> #include <unistd.h> #include "Task.hpp" #include <vector> void* Routine(void* args) { std::string name = static_cast<const char*>(args); while(true) { std::cout << "new thread" << name << std::endl; sleep(1); } } //线程等待 int main() { pthread_t tid; pthread_create(&tid,nullptr,Routine,(void*)"thread_1"); //等待线程退出,否则会出现类似于进程僵尸问题 sleep(5); pthread_cancel(tid);//主动杀掉指定线程 void* retval = nullptr; int n = pthread_join(tid,&retval); if(n == 0)//等待成功 { std::cout << "join success: " << (long long)retval << std::endl; } return 0; }

如果是主线程主动杀掉线程时,线程的退出消息为 -1(PTHREAD_CANCELED);

二、分离线程

默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏。

如果不关心线程的返回值,join 是一种负担,这个时候,我们可以告诉系统,当线程退出时,自动释放线程资源。此时把线程设置成分离状态就行。

#include <iostream> #include <pthread.h> #include <cstdio> #include <stdlib.h> #include <ctime> #include <unistd.h> #include "Task.hpp" #include <vector> void* Routine(void* args) { std::string name = static_cast<const char*>(args); int cnt = 5; while(cnt--) { std::cout << "new thread" << name << std::endl; sleep(1); } return (void*)10; } //线程等待 int main() { pthread_t tid; pthread_create(&tid,nullptr,Routine,(void*)"thread_1"); sleep(2); pthread_detach(tid);//把线程设置分离状态 void* retval = nullptr; int n = pthread_join(tid,&retval); if(n == 0)//等待成功 { std::cout << "join success: " << (long long)retval << std::endl; } else { sstd::cout << "join erroe: " << n << std::endl; } return 0; }

如果把线程设置成分离状态,主线程还要等待线程的话,必然会等待失败。

注意:一旦线程被设置成分离状态,则主线程不能提前退,甚至主线程是个死循环。既然把线程分离了,但是一旦线程出了问题,例如:除0,野指针等,还是会导致整个进程崩掉。

三、线程 ID 和进程地址空间布局

首先 pthread 库也是库,要加载到内存,进而映射到当前进程的虚拟地址空间,以便支持线程控制,在之前的我们可以知道线程的 id 值是一个非常大的数字,现在我可以明确的是线程的 id 值就是一个地址!线程既然可以有多个,那么有的线程正在运行、正在退出、正在分离等,这么多的线程肯定是要被管理起来的,怎么管理?先描述,再组织;注意:这里的线程概念在 Linux 内核中是没有体现出来的,内核里只有轻量级进程的概念,所以线程概念只有在 pthread 库里面体现!所以描述线程,使用 stuct Tcb 来描述,不是在 Linux 内核中描述,而是在 pthread 库里面描述,而组织请看下图:

我们每创建一个线程,在虚拟地址空间里面的 pthread 库就会创建该线程的属性信息,这些属性信息包括:struct pthread 结构体(这个结构体就是上面说的 Tcb)、线程局部存储、线程栈。而线程 id 值或者说 tid 就是这个属性信息的起始地址!!因为每个线程的属性信息在虚拟地址空间上是连续的,所以我们可以联想成是一个数组来管理这些线程的属性信息。在 struct pthread 结构体里面有个变量 void * result 这个变量来存储线程退出的信息,也就是说当 Routine 函数的返回值起始就是赋值给这个变量 result ,而 pthread_join 获取到线程退出的信息也就是把这个变量 result 赋值给我们的传过去的变量。

我们创建线程的底层是调用 clone 系统调用,而 clone 函数是用来创建轻量级进程或者进程的,在 clone 中如果不用创建虚拟地址空间、页表等数据结构,那么就是要创建轻量级进程,即:只创建 PCB ,那么我们可以得出一个结论:每创建一个线程都会在系统层面上有对应的 PCB ,也就是说:struct pthread : struct task_struct = 1 :1,这个称为:1:1 式的用户及线程;而 PCB 承担线程的调度、保存轻量级进程的上下文数据等作用,而 struct pthread 结构体是在库里面描述线程的属性,只是 PCB 承担了线程的大部分属性,所以 struct pthread 里面的属性才会这么少。所以线程退出时,PCB 是直接释放的,所以我们使用 pa -aL 来查线程是查不到的,但是 struct pthread 还存在,所以我们要等待线程释放掉这个 struct pthread 结构体以及该线程的线程局部存储、线程栈。struct pthread 和 线程局部存储、线程栈我们叫做线程的描述信息。

线程局部性存储:

#include <iostream> #include <pthread.h> #include <cstdio> #include <stdlib.h> #include <ctime> #include <unistd.h> #include "Task.hpp" #include <vector> //此时还没有就行线程局部存储 pid_t id = 0; void* Routine(void* args) { std::string name = static_cast<const char*>(args); while (true) { std::cout << "new thread id: " << id << std::endl; id++; sleep(1); } return nullptr; } int main() { pthread_t tid; pthread_create(&tid,nullptr,Routine,(void*)"thread_1"); while (true) { std::cout << "main thread id : " << id << std::endl; sleep(1); } pthread_join(tid,nullptr); return 0; }

上面的出现这张情况的原因:因为线程局部性存储是给每个线程开辟一段栈空间,也就是上面这个 id 值的地址在每个线程的中的虚拟地址是不一样的:

注意:mmap 区域其实就是虚拟地址空间的共享区;上面图片中的线程栈是专门给线程使用的,而上上张图里面的主线程栈是给主线程使用的。

线程局部性存储:只能用来局部性存储内置类型,常见的是整型;

线程局部性存储的意义:可以让不同的线程用同样的变量名,访问不同的内存块,各自访问各自的局部存储。

注意:线程局部性存储这个工作/过程是编译器做的。

四、C++ 的多线程(代码示范)

#include <iostream> #include <unistd.h> #include <thread> void Routine(int cnt) { while (cnt--) { std::cout << "new thread : " << cnt << std::endl; sleep(1); cnt--; } } int main() { std::thread t(Routine,10);//创建线程 while(true) { std::cout << "main thread" << std::endl; sleep(1); } t.join();//线程等待 return 0; }
TestThread:TestThread.cc g++ -o $@ $^ -std=c++11 .PHONY:clean clean: rm -f TestThread

使用C++来编写线程,在 Linux 中能跑,而且把这份代码复制到 Windows 平台下也能跑,这证明 C++ 具有很强的跨平台性(把所有的平台对应的线程代码用 C++ 封装,对外提供统一的接口),C++ 多线程的本质是在Linux系统中 C++ 多线程操作对 pthread 库的封装。

问题:为什么 C++ 性特性支持要以年为单位?

答:从技术角度,他要把所有的平台对应的功能全部封装一遍。

问题:为什么所有语言都追求跨平台性?

答:Windows 和 Linux 背后的用户的百万级别的,如果 C++ 只支持其中的一方,就会导致 C++ 失去了其中一个平台的用户,总之支持跨平台性就是为了让更多用户使用。


未完待续!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/10 1:08:01

用Python脚本自动化调用Sonic生成每日播报视频

用Python脚本自动化调用Sonic生成每日播报视频 在新闻资讯、企业运营和在线教育等领域&#xff0c;每天都有大量需要“开口说话”的内容等待呈现。传统做法是安排真人录制或委托团队剪辑制作——耗时、费力、成本高。而如今&#xff0c;一张静态头像加上一段语音文件&#xff0…

作者头像 李华
网站建设 2026/1/9 3:09:33

Sonic能否被微调训练?目前不开放训练代码

Sonic能否被微调训练&#xff1f;目前不开放训练代码 在虚拟内容爆发式增长的今天&#xff0c;人们对数字人的期待早已从“能动”转向“自然、个性、即用”。无论是品牌打造虚拟代言人&#xff0c;还是教育机构定制专属讲师形象&#xff0c;市场对低成本、高质量、快速生成的说…

作者头像 李华
网站建设 2026/1/7 18:24:01

技术架构:构建对话系统基准测试套件2.0——覆盖五大复杂性维度的设计指南

引子 在人工智能对话系统不断走入商业化、公共服务以及日常生活的今天,衡量一个智能体的真实表现,不能仅靠单一指标或实验室内的封闭场景。用户对话的复杂性远超表面的问答:语义歧义、意图转移、上下文的断裂与再连接、以及对抗性输入的挑战,都会在真实场景中接踵而至。因…

作者头像 李华
网站建设 2026/1/3 19:10:20

公众号推文配套视频?Sonic三分钟搞定

Sonic三分钟搞定公众号推文配套视频&#xff1f;真实体验告诉你怎么用 在公众号运营的日常中&#xff0c;你有没有遇到过这样的场景&#xff1a;文章写好了&#xff0c;逻辑清晰、数据详实&#xff0c;但就是缺个“脸”——没有视频讲解&#xff0c;传播力总差一口气。尤其是知…

作者头像 李华
网站建设 2026/1/2 17:51:53

科研管理系统|基于springboot + vue科研管理系统(源码+数据库+文档)

科研管理系统 目录 基于springboot vue科研管理系统 一、前言 二、系统功能演示 三、技术选型 四、其他项目参考 五、代码参考 六、测试参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 基于springboot vue科研管理系统 一、前言 博主介绍&#xff1a;✌…

作者头像 李华
网站建设 2026/1/10 11:05:00

Sonic数字人支持中文语音吗?完全兼容无压力

Sonic数字人支持中文语音吗&#xff1f;完全兼容无压力 在短视频内容爆炸式增长的今天&#xff0c;一个现实问题摆在了创作者面前&#xff1a;如何以极低的成本&#xff0c;快速生成一条自然流畅、唇形准确的数字人播报视频&#xff1f;传统方案往往依赖3D建模师逐帧调整口型&a…

作者头像 李华