第4篇 Linux多线程–Join vs Detach
前面一篇我们已经谈及主线程和子线程之间的关系,以及线程在运行时的线程状态,本篇我会探讨到如何优雅地连接线程,并且通过一个具体的示例来结合前一篇所说的线程状态来分析不正当使用连接线程带来的负面影响。
线程的连接
我们通过man命令查看一下pthread_join的文档
ss18.png
int pthread_join(pthread_t thread, void **retval);
- 参数thread 就是传入线程的ID
- 参数retval 就是传入线程的返回码,假如线程被取消,那么rval被重置为PTHREAD_CANCELED, 假如调用成功,返回0,失败就返回大于0的整数。
再进一步之前,我们需要理解一下前面没有谈及的线程属性—分离
- 默认情况下,pthread_create创立的进程是非分离的,分离是指一个运行时的线程的一个特定属性,只是告知系统内核该线程结束时,其使用的资源可以回收,其中包括释放所有该线程结束时未释放的系统的资源(包括返回值的内存空间,堆,栈,寄存器等内存空间)。
- 一个没有被分离的线程在结束时,系统内核会保留它的虚拟内存,当中包括它们的堆和栈,寄存器(这种线程,通常叫僵尸线程,那么该僵尸线程的宿主进程,自然就成为僵尸进程)
还有其余线程属性,请参考这个文档
pthread_join调用注意事项
- 调用该系统调用会使传入的线程拥有分离属性,假如该线程已经处于分离的状态,那么调用就会失败。
- 调用该系统调用的线程会一直阻塞,直到指定的线程(用线程ID来标识)调用pthread_exit,从启动的线程回调函数中返回或者者调用pthread_cancel(传入该线程的ID)
下面的例子是一个非常好的例子,非常形象地解析Linux内核的线程管理机制的,首先我们创立了一个代表非负整数序列的结构体Seque,而后在1到200分成三个长度不同的数据区间,分别传递给三个线程。
#include <stdio.h>#include <sys/types.h>#include <unistd.h>#include <stdlib.h>#include <pthread.h>#include<string.h>typedef struct{ unsigned long begin; unsigned long end; unsigned long sum;} Sequen;void* calc(void* args){ Sequen* se=(Sequen*)args; unsigned long beg=se->begin; unsigned long end=se->end; pthread_t tid=pthread_self(); //计算奇数和 for(unsigned long i=beg;i<=end;i+=2){ se->sum+=i; } printf("子线程0x%lx ->计算结果:%lu\n",tid,se->sum); return NULL;}int main(int argc,char* argv){ pthread_t tids[3]; Sequen seq[3]={ {1,50,0}, {51,100,0}, {101,200,0} }; int err; for(int i=0;i<3;i++){ printf("创立地第%d个子线程\n",i+1); err=pthread_create(&tids[i],NULL,calc,&seq[i]); if (err){ printf("创立线程失败!!\n"); continue; } pthread_join(tids[i],NULL); } printf("主线程计算结果:%ld\n",seq[0].sum+seq[1].sum+seq[2].sum);}输出结果:你觉得很奇怪是吗?为什么三个子线程的ID是一样的呢?

我们分析一下这里的三个线程发生了什么事情,首先我们在for循环中分别使用pthread_create创先后创立了三个进程(注意:字眼是“先后”代表是有顺序)
- 第一个被创立的线程,系统从线程ID的编号空间中分配一个线程ID(十六进制)0x7f1657e2e700给tids[0],随后立即将该子线程传递给pthread_join解决,此时第一个线程创立之后,但未被pthread_join解决完之前,该线程的状态是“运行”了。注意,这个phread_join是主线程调用的,因而主线程进入“阻塞”状态,此时主线程只能等第一个子线程结束并且什么事做不了,因而也无法创立剩下的两个子线程。
- 当第一个线程终止后,主线程从“阻塞”回到“运行”状态,而且在第一个子线程结束后,系统内核回收了第一个子线程ID:0x7f1657e2e700,并将该ID再“转租”给第二个子线程。因而第一个子线程和第二个子线程的线程ID是一样的。当主线程再次调用pthread_join后,那么主线程再次从“运行”状态切换到“阻塞”状态,一直等到第二个子线程执行完毕,才能接着创立第三个子线程。
- 同理,第三个子线程也和前面两个步骤的分析一样。
你由于这个分析不正当吗?我也查阅了gnu官网的相关文档说明https://www.gnu.org/software/libc/manual/html_node/Process-Identification.html
ss18.png
中文翻译:
在Linux上,由pthread_create创立的线程也会收到线程ID。 初始(主)线程的线程ID与整个进程的进程ID相同。 随后创立的线程的线程ID是不同的。 它们是从与进程ID相同的编号空间分配的。 有时,进程ID和线程ID也统称为任务ID。 与进程相比,线程从不显式等待,因而线程ID在线程退出或者取消后立就可以重用。 即便对于可连接线程,也不仅仅是分离线程,都是如此。 线程被分配给线程组。 在Linux上运行的GNU C库实现中,进程ID是进程中所有线程的线程组ID。
phread_join的反作用
OK,phread_join调用需要能够使改变目标子线程的线程属性,但它显著的反作用是调用它的函数上下文所处的线程处于阻塞状态,调用phread_join的线程无法执行接下来的其余指令。
试问上面的示例跟一个单线程的同步调用没什么区别!~由于主线程的间隔性的堵塞,令到子线程没办法连续创立和并发运行。因而,我们使用在多线程编程中,要谨慎使用phread_join调用,我们应该结合我们具体的上下问逻辑来正当使用phread_join系统调用.
Join vs Detach
后续继续升级….
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 第4篇 Linux多线程–Join vs Detach