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

Linux操作系统之线程:线程控制

前言:

上一篇文章我们着重对线程他的共享代码这个特点进行了论述,讲解了部分性质与容易出现的问题。

那么现在我们本篇文章就更加深层次的来学习一下线程吧!

一、上文补充

我们说线程的绝大部分资源都是共享的,这句话其实不是很完善。

最准确的说法,线程之间一切都是共享的。

有人说:不是还有独立的栈结构吗?

其实,栈结构的独立性是编程语言或操作系统提供的逻辑保护,而非物理隔离。他只是不想让你看见,所以说如果你非要看见,是有办法的:

代码语言:javascript

AI代码解释

#include<iostream> #include<pthread.h> #include<unistd.h> int *ptr=nullptr; void* func1(void*_name) { std::string name=static_cast<const char*>(_name); int a=100;//临时变量,在栈上面 ptr=&a; while(true) { std::cout<<name<<"线程运行: "<<a<<std::endl; sleep(1); } } void* func2(void *_name) { std::string name=static_cast<const char*>(_name); while(true) { if(ptr!=nullptr)//防止func2先运行此时ptr还为空直接越界访问 std::cout<<name<<"线程运行: "<<(*ptr)++<<std::endl; sleep(1); } } int main() { pthread_t tid1,tid2; pthread_create(&tid1,nullptr,func1,(void*)"pthread-1"); pthread_create(&tid2,nullptr,func2,(void*)"pthread-2"); pthread_join(tid1,nullptr); pthread_join(tid2,nullptr); return 0; }

可以看见这个代码中,我们在func一的线程中初始化了一个局部变量,但是我们可以通过上节课讲的共享的特性,用全局变量指针,来让多个线程之间看到同一份资源。


二、线程终止

我们之前一开始就给大家讲解了线程的创建,大家也就知道了在linux中严格意义上来说是没有线程的概念的,只有轻量级进程。

而我们的pthread_create函数内部其实是封装了clone,clone这个函数实际上是用来创建轻量级进程的。为了产生线程这个概念,我们就在轻量级进程上面进行了一层封装,这也就是pthread_create的由来。

而pthread_create这个函数被各大语言封装成了各大语言(C++,java)线程接口,但是由于底层是pthread_create,所以我们在编译时都要链接上我们的pthread库。

回顾完我们线程创建的知识,现在我们来了解一下线程退出的几个方法:

我们手动给主副线程最后结束返回return可以知道,主线程使用return会导致所有线程都退出,也就是理论上的进程退出了。而副线程的return只会导致副线程退出。(这就跟我们调用了一个函数return返回一样,不会影响main函数)

那么exit呢?这个函数我们在讲进程退出时说过,这个函数会导致进程之间退出。那么大家就可以预料到了,无论是在主线程还是副线程中调用exit,都会导致所以线程立马退出,也就相当于进程退出。

正如同进程有专门的exit函数来退出,线程也有专门的函数调用来退出线程:

pthread_exit函数的作用就是终止调用它的线程,并可选地返回一个值(线程的退出状态)。

代码语言:javascript

AI代码解释

void* func(void* arg) { printf("子线程正在运行\n"); pthread_exit((void*)42); // 终止线程并返回值42 } int main() { pthread_t tid; void* retval; pthread_create(&tid, NULL, func, NULL); pthread_join(tid, &retval); // 获取子线程的退出值 printf("子线程返回: %ld\n", (long)retval); // 输出42 return 0; }

值得一提的是,在多线程编程中,当线程通过pthread_exitreturn返回一个指针时,必须确保该指针指向的内存是全局变量或堆内存(malloc分配),而不能是线程栈上的局部变量。这是因为线程栈的生命周期与线程绑定,线程退出后,其栈内存会被回收,导致返回的指针指向无效内存(悬垂指针),引发未定义行为(如数据损坏或程序崩溃)。


除了这个函数来退出线程之外,我们还可以取消线程:

pthread_cancel 是 POSIX 线程库中用于请求取消另一个线程的函数。它的作用类似于向目标线程发送一个“终止请求”,但具体是否终止、何时终止以及如何清理资源,取决于目标线程的取消状态和清理处理机制。

这个函数,通常是用我们的主线程调用,来取消副线程,取消的线程的返回值为-1。

代码语言:javascript

AI代码解释

void*func(void* argv) { while(true) { std::cout<<"子线程运行中 "<<std::endl; sleep(1); } } int main() { pthread_t tid; pthread_create(&tid, nullptr, func, nullptr); sleep(5); pthread_cancel(tid); void* ret=nullptr; pthread_join(tid, &ret); std::cout<<"子线程退出信息:"<<(long long int)ret<<std::endl; return 0; }

这里我们要注意什么呢?首先,你要取消一个线程,这个线程必须要先被启动了。并且,被你取消的进程一定要用join回收,否则会导致资源泄漏。

所以,我们就一定要谨慎调用这个函数。


三、线程等待

我们之前已经讲过线程等待函数pthread_join,所以我们这里不再过多赘述,我在这里补充一些内容。

那如果我们的主线程要做自己事情呢?

我们之前说过,线程等待会阻塞进程。有没有什么办法让主线程不阻塞呢?

有的:我们可以切换线程等待的状态。

一个线程有两种被等待的状态:

1、joined:线程需要join(默认状态)

2、detach:线程分离(主线程不必等待副线程)

我们可以调用pthread_detach函数使一个副线程的状态变为detach分离状态:

的作用是告诉操作系统:当该线程结束时,系统自动回收其资源,无需其他线程调用 pthread_join来等待它 。线程结束后,系统自动回收其资源,其他线程无法再调用 pthread_join等待它(调用会失败)。

但是这也会给我们带来一些问题:如果我们的主线程先退出了,副线程还没退出,并且副线程此时是分离状态呢?这会导致副线程直接退出(大部分系统下)

所以我们一定要保证主线程比副线程后退出,如果不能保证,就不要分离线程。

四、线程的exec问题

我们想问一下,当我们新建了一个线程之后,还能进行exec吗?此时exec会造成什么后果呢?

exec会 完全替换当前进程的地址空间,包括所有线程(无论是否分离),而线程共享同一个进程地址空间,所以其他线程的执行会被强制中断,且 没有机会执行清理操作。

所以我们不能使用exec的调用接口。

那如果我们想使用exec函数,该怎么办呢?

答案是:fork!!

没错,就算是在副线程中,我们也可以调用fork接口,创造一个新进程!!

随后,就能在这个进程中使用exec的调用接口了。

当我们在副线程中调用fork,他只会复制当前线程。也就是说,新创建的进程内只会有一个PCB。

总结:

兄弟们,线程这玩意儿就是个共享怪胎,表面上说栈是独立的,但实际上我用个全局指针就能偷看其他线程的栈数据!创建线程底层就是个clone系统调用,各大语言都是套了层皮而已。

线程退出的姿势也可多:主线程return直接带崩全场,副线程return就跟函数返回一样乖巧。pthread_exit能优雅退场还能留个遗言,但记住别返回局部变量的地址,不然分分钟给你来个悬垂指针的惊喜!

pthread_cancel这货就是个线程杀手,一枪崩了目标线程,但记得一定要用join收尸,不然资源泄漏有你受的。如果想不阻塞主线程?detach一下就行,但主线程要是先溜了,子线程直接凉凉。所以要保证退出顺序哦~最坑爹的是exec,这玩意儿一调用,管你几个线程统统完蛋!想用exec?先fork个新进程再说。不过fork也是个坑货,只复制当前线程,其他线程的锁啊资源啊全都不管了,死锁警告!总之,玩线程就是在刀尖上跳舞,一个不小心就翻车!

http://www.jsqmd.com/news/512067/

相关文章:

  • 电机控制必学:Clarke和Park变换的5分钟快速记忆法(附MATLAB验证代码)
  • Bambu Studio 3D打印切片软件:从入门到精通的完整指南
  • STM32_ADC_寄存器操作
  • 基于RABC的权限控制设计
  • 数据库设计原则
  • Qwen2.5-VL-7B-Instruct保姆级教程:对话历史管理、一键清空操作
  • 总结上海移民中介服务费用情况,多少钱才合理 - mypinpai
  • WangEditor — 轻量级富文本编辑器的核心功能与实战应用
  • 【技术干货】MiniMax M2.7 自主进化多智能体模型:从原理到实战落地指南
  • Pixel Dimension Fissioner部署教程:GPU算力优化适配+免配置镜像实操
  • RADIUS协议实战解析:从RFC2865/2866到典型配置与报文深度剖析
  • Pixel Dimension Fissioner快速上手:像素UI交互+实时HUD状态监控详解
  • 如何用AI读脸术做实时分析?CPU推理优化实战案例详解
  • SEO_如何通过内容优化有效提升SEO效果?(143 )
  • 2026年怀化透析中心排名,附近透析中心收费标准与服务质量对比 - myqiye
  • Qwen-Image镜像一键部署:预装CUDA12.4+PyTorch+Qwen-VL依赖的极简方案
  • AudioLDM-S生成效果实测对比:10步和50步有什么区别?听音频就知道
  • 2026年市场上有实力的磁性材料成型液压机源头厂家推荐榜单,稀土永磁压制/铁氧体成型/磁性材料湿法成型/自动化生产线,磁性材料成型液压机制造企业哪家好 - 品牌推广师
  • Kubernetes存储与GPU管理:从开源到主流云厂商的最佳实践
  • Jimeng AI Studio(Z-Image Edition)入门教程:环境部署与快速调用指南
  • 测貂图片AI工具时,模板多不等于后面好改
  • 2026年浙江地区好用的弯管机厂家推荐,专业厂商盘点 - 工业推荐榜
  • 【工业级C语言OTA健壮性设计】:基于CRC32+SHA256+版本指纹三重校验的失败分级响应策略
  • Python实战:利用莱斯利模型预测种群动态变化
  • # 发散创新:用Python打造自动化渗透测试工具链——从扫描到漏洞利用全流程实战在现代信息安全
  • LVGL 7.10.1在STM32F103上的嵌入式GUI移植实战
  • Qwen3-32B-Chat开源模型部署新范式:单卡24G实现32B参数高效推理
  • 总结哈尔滨自动变速箱维修推荐,怎么选择合适的公司? - 工业设备
  • FFmpeg编解码实战
  • SEO_10个提升网站排名的实用SEO技巧分享(470 )