Linux操作系统之线程:线程控制
前言上一篇文章我们着重对线程他的共享代码这个特点进行了论述讲解了部分性质与容易出现的问题。那么现在我们本篇文章就更加深层次的来学习一下线程吧一、上文补充我们说线程的绝大部分资源都是共享的这句话其实不是很完善。最准确的说法线程之间一切都是共享的。有人说不是还有独立的栈结构吗其实栈结构的独立性是编程语言或操作系统提供的逻辑保护而非物理隔离。他只是不想让你看见所以说如果你非要看见是有办法的代码语言javascriptAI代码解释#includeiostream #includepthread.h #includeunistd.h int *ptrnullptr; void* func1(void*_name) { std::string namestatic_castconst char*(_name); int a100;//临时变量在栈上面 ptra; while(true) { std::coutname线程运行: astd::endl; sleep(1); } } void* func2(void *_name) { std::string namestatic_castconst char*(_name); while(true) { if(ptr!nullptr)//防止func2先运行此时ptr还为空直接越界访问 std::coutname线程运行: (*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函数内部其实是封装了cloneclone这个函数实际上是用来创建轻量级进程的。为了产生线程这个概念我们就在轻量级进程上面进行了一层封装这也就是pthread_create的由来。而pthread_create这个函数被各大语言封装成了各大语言Cjava线程接口但是由于底层是pthread_create所以我们在编译时都要链接上我们的pthread库。回顾完我们线程创建的知识现在我们来了解一下线程退出的几个方法我们手动给主副线程最后结束返回return可以知道主线程使用return会导致所有线程都退出也就是理论上的进程退出了。而副线程的return只会导致副线程退出。这就跟我们调用了一个函数return返回一样不会影响main函数那么exit呢这个函数我们在讲进程退出时说过这个函数会导致进程之间退出。那么大家就可以预料到了无论是在主线程还是副线程中调用exit都会导致所以线程立马退出也就相当于进程退出。正如同进程有专门的exit函数来退出线程也有专门的函数调用来退出线程pthread_exit函数的作用就是终止调用它的线程并可选地返回一个值线程的退出状态。代码语言javascriptAI代码解释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_exit或return返回一个指针时必须确保该指针指向的内存是全局变量或堆内存malloc分配而不能是线程栈上的局部变量。这是因为线程栈的生命周期与线程绑定线程退出后其栈内存会被回收导致返回的指针指向无效内存悬垂指针引发未定义行为如数据损坏或程序崩溃。除了这个函数来退出线程之外我们还可以取消线程pthread_cancel 是 POSIX 线程库中用于请求取消另一个线程的函数。它的作用类似于向目标线程发送一个“终止请求”但具体是否终止、何时终止以及如何清理资源取决于目标线程的取消状态和清理处理机制。这个函数通常是用我们的主线程调用来取消副线程取消的线程的返回值为-1。代码语言javascriptAI代码解释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* retnullptr; pthread_join(tid, ret); std::cout子线程退出信息(long long int)retstd::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.coloradmin.cn/o/2433531.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!