线程概念与控制(中)
本篇目标1.线程库的引入与理解2.验证之前的概念3.知道如何创建线程终止线程等待线程和分离线程一.Linux线程控制1.引入线程库1.1.创建线程通过之前对线程的理解我们已经知道OS中有这么个执行流了那么如何验证它是真的存在呢那么就需要接下来的操作首先我们需要创建一个线程就需要用到pthread_create函数如代码原型 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);参数解析 :thread: 返回线程 ID注意在很多 Linux 环境下pthread_t底层可能是typedef unsigned long int pthread_t;attr: 设置线程的属性 attr 为 nullptr 表示使用默认属性start_routine: 是个函数地址线程启动后要执行的函数其实就是新线程要执行的函数入口arg: 传给线程启动函数的参数我们也可以在linux终端里面输入man pthread_create如图所示使用时要包含头文件pthread返回值成功返回 0 失败返回错误码。代码演示在test.cpp中#include iostream #include string #include unistd.h #include pthread.h void*threadrun(void*arg) { std::string namestatic_castconst char*(arg); int cnt10; while(cnt--) { std::coutI am namestd::endl; sleep(1); } return nullptr; } int main() { pthread_t tid; pthread_create(tid,nullptr,threadrun,(char*)new thread); int count10; while(count--) { std::coutI am main threadstd::endl; sleep(1); } return 0; }在makefile中test:test.cpp g -o $ $^ -stdc11 .PHONY:clean clean: rm -rf test结果如图这个为什么会报错呢其实是因为头文件找到了函数声明认识但链接阶段找不到函数实现也就是说g找不到phread_create函数的实现位置但真正的函数实现在线程库里所以链接时要加-lpthread此时结果1.2.线程库然后我们可以再重新开一个窗口重新执行上面这个可执行程序然后输入ps -aL就可以查看线程的具体信息了。此时的打印结果和另外一个窗口的输出解释其实这个打印出来的tid是通过pthread库中的函数pthread_self得到的它返回⼀个pthread_t类型的变量指代的是调⽤pthread_self函数的线程的“ID”。怎么理解这个“ID”呢这个“ID”是pthread库给每个线程定义的进程内唯⼀标识是由pthread库维持的。 由于每个进程有⾃⼰独⽴的内存空间故此“ID”的作⽤域是进程级⽽非系统级内核不认识。 其实pthread库也是通过内核提供的系统调用例如clone来创建线程的而内核会为每个线程创建系统全局唯⼀的“ID”来唯⼀标识这个线程。而这个ps -aL输出的结果我们可以明白的是PID毕竟两个线程同属于一个进程但是这个LWP又是什么东西呢其实这个LWP是缩写的全称是light weight process即轻量级进程之前使⽤ pthread_self 得到的这个数实际上是⼀ 个地址在虚拟地址空间上的⼀个地址通过这个地址可以找到关于这个线程的基本信息包括线 程ID线程栈寄存器等属性。在 ps-aL 得到的线程ID有⼀个线程ID和进程ID相同这个线程就是主线程主线程的栈在虚拟 地址空间的栈上而其他线程的栈在是在共享区堆栈之间因为pthread系列函数都是pthread库 提供给我们的。而pthread库是在共享区的。所以除了主线程之外的其他线程的栈都在共享区。那么CPU调度时到底是看lwp?还是pid呢其实在Linux 里CPU 调度时看的不是“进程 PID 整体”而是看具体的执行流也就是 LWP / 线程对应的调度实体也就是说Linux CPU 调度时真正调度的是 LWP / task_struct而不是抽象的整个进程。例如一个进程里面有 3 个线程进程 PID 1000 线程1 LWP 1000 线程2 LWP 1001 线程3 LWP 1002CPU 调度时不会说“我要调度 PID 1000 这个进程整体。”而是说“我要调度 1000 / 1001 / 1002 里面某一个具体执行流。”但是这里还有个疑问为什么会有这个库先看张图片解释Linux 内核本身并没有给用户直接提供一个“真正意义上的线程对象”。Linux 内核更核心的调度对象是task_struct / 轻量级进程 / LWP也就是说从 OS 角度看它不是说我创建了一个高级语言意义上的 thread。而是说我创建了一个可以被 CPU 调度的执行流这个执行流在内核中用 task_struct 描述。那么中间就需要一个东西把用户想要的“线程接口”翻译成 Linux 内核能理解的“轻量级进程创建”。这个东西就是pthread库它做的事情就是用户调用 pthread_create ↓ pthread 库封装参数、栈、属性、启动函数 ↓ 底层通过系统调用创建轻量级进程 / LWP ↓ OS 调度这个 LWP所以pthread库存在的原因就是用户想用线程但 Linux 内核底层提供的是轻量级进程所以需要 pthread 库在用户层把“线程接口”封装出来。总结Linux 内核并不直接向用户提供传统意义上的线程接口内核真正调度的是轻量级进程。可是从用户角度来看我们更希望使用“线程”这个概念来编写程序。因此在 user 和 OS 之间就需要 pthread 库对线程接口进行封装。用户调用 pthread_create 时实际上是通过 pthread 库进一步调用底层机制让 OS 创建出一个轻量级进程。也就是说pthread 库的作用就是在用户层提供线程接口在底层对接 Linux 的轻量级进程实现。2.线程控制的接口2.1.线程等待为什么需要线程等待• 已经退出的线程其空间没有被释放仍然在进程的地址空间内。• 创建新的线程不会复用刚才退出线程的地址空间。在终端里面输入man pthread_join即可查看线程等待的信息如图参数 :thread: 线程 IDvalue_ptr: 它指向⼀个指针后者指向线程的返回值返回值成功返回 0 失败返回错误码通过 pthread_join得到的终止状态是不同的总结如下:1.如果thread线程通过return返回value_ptr所指向的单元⾥存放的是thread线程函数的返回值。2. 如果thread线程被别的线程调用pthread_cancel异常终掉value_ptr所指向的单元⾥存放的是常数PTHREAD_CANCELED。3. 如果thread线程是⾃⼰调用pthread_exit终⽌的value_ptr所指向的单元存放的是传给 pthread_exit的参数。4. 如果对thread线程的终止状态不感兴趣可以传nullptr给value_ptr参数。当然等下的代码演示主要是以return 为主的demo如图#include iostream #include string #include unistd.h #include pthread.h void *threadrun( void *arg ) { printf(thread 1 returning ... \n); int*pnew int(1); return (void*)p; } int main() { pthread_t tid; pthread_create(tid,nullptr,threadrun,(char*)new thread); int count1; while(count--) { std::coutI am main threadstd::endl; sleep(1); } void*ptr; pthread_join(tid,ptr); printf(thread return, thread id:%lu, return code:%d\n, tid, *(int*)ptr); delete (int*)ptr; return 0; }输出结果2.2.线程终止如果需要只终止某个线程而不终止整个进程可以有三种⽅法:1. 从线程函数return。这种⽅法对主线程不适⽤从main函数return相当于调⽤exit。2. 线程可以调用pthread_exit终止自己但是不要在新线程执行的函数里面直接exit()否则这个进程是会直接退出的pthread_join也是接受不到退出码的例如#include iostream #include string #include unistd.h #include pthread.h void *threadrun( void *arg ) { printf(thread 1 returning ... \n); int*pnew int(1); exit(1); return (void*)p; } int main() { pthread_t tid; pthread_create(tid,nullptr,threadrun,(char*)new thread); int count1; while(count--) { std::coutI am main threadstd::endl; sleep(1); } void*ptr; pthread_join(tid,ptr); printf(thread return, thread id:%lu, return code:%d\n, tid, *(int*)ptr); delete (int*)ptr; return 0; }输出结果此时我们需要用到pthread_exit函数如图参数value_ptr线程退出时留给其他线程的返回值。#include iostream #include string #include unistd.h #include pthread.h void *threadrun( void *arg ) { printf(thread 1 returning ... \n); int*pnew int(3); pthread_exit((void*)p); } int main() { pthread_t tid; pthread_create(tid,nullptr,threadrun,(char*)new thread); int count1; while(count--) { std::coutI am main threadstd::endl; sleep(1); } void*ptr; pthread_join(tid,ptr); printf(thread return, thread id:%lu, return code:%d\n, tid, *(int*)ptr); delete (int*)ptr; return 0; }输出结果注意pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配因为当其它线程得到这个返回指针时线程函数已经退出了。3. ⼀个线程可以调⽤pthread_cancel终⽌同⼀进程中的另⼀个线程如图功能取消⼀个执⾏中的线程参数thread: 线程 ID返回值成功返回 0 失败返回错误码demo如图#include iostream #include string #include unistd.h #include pthread.h void *threadrun( void *arg ) { printf(thread 1 returning ... \n); int*pnew int(3); return (void*)p; } int main() { pthread_t tid; pthread_create(tid,nullptr,threadrun,(char*)new thread); pthread_cancel(tid); int count1; while(count--) { std::coutI am main threadstd::endl; sleep(1); } void*ptr; pthread_join(tid,ptr); printf(thread return, thread id:%lu, return code:%d\n, tid, *(int*)ptr); delete (int*)ptr; return 0; }执行结果原因我们调用了pthread_cancel(tid);但是线程被取消成功那么pthread_join拿到的返回值不是p而是PTHREAD_CANCELED它通常是一个特殊指针值类似(void*)-1所以你再执行*(int*)ptr就相当于访问非法地址自然可能报错。修改后的代码#include iostream #include string #include unistd.h #include pthread.h void *threadrun(void *arg) { printf(thread 1 returning ... \n); int *p new int(3); return (void *)p; } int main() { pthread_t tid; pthread_create(tid, nullptr, threadrun, (char *)new thread); pthread_cancel(tid); int count 1; while (count--) { std::cout I am main thread std::endl; sleep(1); } void *ptr; pthread_join(tid, ptr); if (ptr PTHREAD_CANCELED) { printf(thread was canceled, return: PTHREAD_CANCELED,ptr %p\n, ptr); } else { printf(thread return, thread id:%lu, return code:%d\n,(unsigned long)tid, *(int *)ptr); delete (int *)ptr; } return 0; }执行结果注意取消的时候一定要保证新线程已经启动了并且一般是主线程取消新线程而新线程取消主线程一般是不常用的。2.3.分离线程• 默认情况下新创建的线程是joinable的线程退出后需要对其进行pthread_join操作否则无法释放资源从而造成系统泄漏。• 如果不关心线程的返回值join是⼀种负担这个时候我们可以告诉系统当线程退出时⾃ 动释放线程资源。可以是线程组内其他线程对目标线程进行分离也可以是线程自己分离pthread_detach(pthread_self());如图参数thread: 线程 ID返回值成功返回 0 失败返回错误码demo如代码#include iostream #include string #include unistd.h #include pthread.h void *threadrun(void *arg) { printf(thread 1 returning ... \n); int *p new int(3); return (void *)p; } int main() { pthread_t tid; pthread_create(tid, nullptr, threadrun, (char *)new thread); pthread_detach(tid); int count 1; while (count--) { std::cout I am main thread std::endl; sleep(1); } void *ptr; pthread_join(tid, ptr); printf(thread return, thread id:%lu, return code:%d\n,(unsigned long)tid, *(int *)ptr); delete (int *)ptr; return 0; }执行结果原因pthread_join 和 pthread_detach两者冲突了所以想拿线程返回值就 join想让线程自动回收就 detach但 detach 后不能再 join。新代码#include stdio.h #include stdlib.h #include string.h #include unistd.h #include pthread.h void *thread_run(void *arg) { pthread_detach(pthread_self()); printf(%s\n, (char *)arg); return NULL; } int main(void) { pthread_t tid; int ret 0; if (pthread_create(tid, NULL, thread_run,(char*) thread1 run...) ! 0) { printf(create thread error\n); return 1; } // 很重要要先让新线程运行并在线程内部把自己设置为分离状态 sleep(1); if (pthread_join(tid, NULL) 0) { printf(pthread wait success\n); ret 0; } else { printf(pthread wait failed\n); ret 1; } return ret; }执行结果总结本篇主要学习了 Linux 线程控制相关接口。首先通过pthread_create创建线程验证了进程内部可以存在多个执行流。用户层使用的是 pthread 库提供的线程接口而 Linux 内核真正调度的是 LWP / task_struct因此 pthread 库起到了 user 和 OS 之间的封装作用。随后学习了pthread_join线程等待它可以等待线程退出并获取线程的返回值。线程终止可以通过return、pthread_exit或pthread_cancel实现其中线程被取消后pthread_join得到的返回值是PTHREAD_CANCELED不能当作普通指针解引用。最后学习了pthread_detach线程分离。默认线程是 joinable 状态需要 join 回收资源如果不关心线程返回值可以将线程设置为 detached 状态让系统在线程退出后自动回收资源。但需要注意pthread_join和pthread_detach不能同时使用。

相关新闻

GPT-5首批17家灰度合作伙伴技术简报解密(含非公开latency benchmark、function calling失败率热力图与fallback降级策略)

GPT-5首批17家灰度合作伙伴技术简报解密(含非公开latency benchmark、function calling失败率热力图与fallback降级策略)

更多请点击: https://intelliparadigm.com 第一章:GPT-5灰度发布全景图:17家合作伙伴生态与战略定位 GPT-5灰度发布并非单点技术交付,而是一场覆盖多行业、多场景、多层级的协同演进。OpenAI联合全球17家头部企业启动分阶段、分区…

2026/6/29 12:29:18阅读更多 →
TLF35584电源管理芯片实战解析(一):从引脚配置到系统安全设计

TLF35584电源管理芯片实战解析(一):从引脚配置到系统安全设计

1. TLF35584电源管理芯片的核心功能解析 第一次拿到TLF35584这颗电源管理芯片(PMIC)时,我盯着密密麻麻的引脚图有点发懵。作为汽车电子项目中的"电力调度中心",它需要同时处理主控MCU供电、通信模块稳压、传感器电源跟踪…

2026/6/29 12:29:18阅读更多 →
HICO/HICO-Det 数据集:从标注结构到HOI任务实践指南

HICO/HICO-Det 数据集:从标注结构到HOI任务实践指南

1. HICO与HICO-Det数据集基础解析 第一次接触HICO数据集时,我被它庞大的标注体系震撼到了。这个专为HOI(人物-物体交互)任务设计的数据集,包含了600种动名词组合、80类物体和117种行为,几乎覆盖了日常生活中所有常见交…

2026/6/29 12:29:18阅读更多 →
如何使用oec-hardware快速验证服务器与openEuler兼容性:完整指南 [特殊字符]

如何使用oec-hardware快速验证服务器与openEuler兼容性:完整指南 [特殊字符]

如何使用oec-hardware快速验证服务器与openEuler兼容性:完整指南 🚀 【免费下载链接】oec-hardware Use for check hardware compatibility with openEuler 项目地址: https://gitcode.com/openeuler/oec-hardware 前往项目官网免费下载&#xff…

2026/6/29 16:30:31阅读更多 →
Magisk V24.1 源码编译实战:从环境配置到APK生成的完整避坑指南

Magisk V24.1 源码编译实战:从环境配置到APK生成的完整避坑指南

1. 环境准备:搭建编译Magisk的基石 第一次编译Magisk源码时,最让人头疼的往往不是代码本身,而是环境配置。我清楚地记得去年帮同事搭建环境时,光是解决JDK版本冲突就花了整整一下午。下面这些血泪经验,能让你少走至少…

2026/6/29 16:30:31阅读更多 →
AI 正如何重塑知识密集型行业:写给技术人的趋势观察引言

AI 正如何重塑知识密集型行业:写给技术人的趋势观察引言

2026年,我们正在见证一个显著的技术趋势:大语言模型不再停留在对话和文本生成阶段,开始深度渗透进医疗、法律、教育等传统上高度依赖个人经验的行业。这不是简单的“机器换人”,而是一次关于知识、经验与判断力如何被重新定价的结…

2026/6/29 16:30:31阅读更多 →
终极Sakura启动器:5分钟搞定AI翻译模型部署

终极Sakura启动器:5分钟搞定AI翻译模型部署

终极Sakura启动器:5分钟搞定AI翻译模型部署 【免费下载链接】Sakura_Launcher_GUI Sakura模型启动器 项目地址: https://gitcode.com/gh_mirrors/sa/Sakura_Launcher_GUI 还在为复杂的AI模型部署而烦恼吗?Sakura启动器GUI让你的AI翻译模型部署变得…

2026/6/29 16:30:31阅读更多 →
Alternative Mod Launcher (AML):彻底解决XCOM 2模组管理难题的终极方案

Alternative Mod Launcher (AML):彻底解决XCOM 2模组管理难题的终极方案

Alternative Mod Launcher (AML):彻底解决XCOM 2模组管理难题的终极方案 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode…

2026/6/29 16:30:31阅读更多 →
如何5分钟实现STL到STEP格式转换:从网格到实体的专业蜕变指南

如何5分钟实现STL到STEP格式转换:从网格到实体的专业蜕变指南

如何5分钟实现STL到STEP格式转换:从网格到实体的专业蜕变指南 【免费下载链接】stltostp Convert stl files to STEP brep files 项目地址: https://gitcode.com/gh_mirrors/st/stltostp 你是否遇到过这样的困扰?精心设计的3D打印模型在STL格式下…

2026/6/29 16:25:31阅读更多 →
AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

AI Coding 六个月真实ROI账本:产品经理的血泪教训,研发的冷静忠告

6个月前的2025年12月,Boris Cherny 公开宣布自己卸载了 IDE。一时间,Vibe Coding 成了全行业最热的话题。6个月后,当我们回过头来拉一份真实账本,发现事情远没有"一句话生成一个App"那么浪漫。本文从产品经理和研发两个…

2026/6/29 3:27:55阅读更多 →
审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

审计来了,数据权限全开——审计走了,怎么确保权限全部关掉?

引言:审计结束三个月了,审计员的权限还没关某城商行每年按照监管要求开展至少一次数据安全审计。审计期间,内审部门需要抽样检查各类业务数据——交易流水、客户信息、员工操作日志、权限配置记录。这些数据分布在不同系统中,审计…

2026/6/29 2:19:08阅读更多 →
如何在3秒内从普通图片生成专业级法线贴图:DeepBump的终极指南

如何在3秒内从普通图片生成专业级法线贴图:DeepBump的终极指南

如何在3秒内从普通图片生成专业级法线贴图:DeepBump的终极指南 【免费下载链接】DeepBump Normal & height maps generation from single pictures 项目地址: https://gitcode.com/gh_mirrors/de/DeepBump 还在为3D建模中的纹理制作而烦恼吗?…

2026/6/29 0:01:47阅读更多 →
OCAuxiliaryTools:终极OpenCore配置工具,让黑苹果安装从未如此简单!

OCAuxiliaryTools:终极OpenCore配置工具,让黑苹果安装从未如此简单!

OCAuxiliaryTools:终极OpenCore配置工具,让黑苹果安装从未如此简单! 【免费下载链接】OCAuxiliaryTools Cross-platform GUI management tools for OpenCore(OCAT) 项目地址: https://gitcode.com/gh_mirrors/oc/OCA…

2026/6/29 0:01:47阅读更多 →
终极Windows 11精简指南:使用tiny11builder快速创建纯净系统镜像

终极Windows 11精简指南:使用tiny11builder快速创建纯净系统镜像

终极Windows 11精简指南:使用tiny11builder快速创建纯净系统镜像 【免费下载链接】tiny11builder Scripts to build a trimmed-down Windows 11 image. 项目地址: https://gitcode.com/GitHub_Trending/ti/tiny11builder 你是否厌倦了Windows 11系统自带的20…

2026/6/29 0:01:47阅读更多 →