pthread线程传递数据回主线程_第4篇 Linux系统编程-优雅地调用多个线程

e1e108d19619d5ec33784090d35407ab.png

前面一篇我们已经谈及主线程和子线程之间的关系,以及线程在运行时的线程状态,本篇我会讨论到如何优雅地连接线程,并且通过一个具体的示例来结合前一篇所说的线程状态来分析不合理使用连接线程带来的负面影响。

线程的属性

再进一步之前,我们需要了解一下线程属性,线程在创建之初按照调用Linux的系统API的不同--分为两种属性连接分离

首先看看线程的连接属性,我们通过man命令查看一下pthread_join的文档

ad66b4cbbd204dbe86495b846393616a.png
int pthread_join(pthread_t thread, void **retval);
  • 参数thread 就是传入线程的ID
  • 参数retval 就是传入线程的返回码,如果线程被取消,那么rval被重置为PTHREAD_CANCELED, 如果调用成功,返回0,失败就返回大于0的整数。

默认情况下,pthread_create创建的进程是可链接(join)的,分离(detach)是指一个运行时的线程的一个特定属性,只是告知系统内核该线程结束时,其使用的资源可以回收,其中包括释放所有该线程结束时未释放的系统的资源(包括返回值的内存空间,堆,栈,寄存器等内存空间)。

  • 一个未被分离(即-连接)的线程在结束或被外界强制终止时,系统内核会保留它的虚拟内存,当中包括它们的堆和栈,寄存器。这种线程,通常叫僵尸线程,那么拥有该僵尸线程的宿主进程,自然就成为僵尸进程
  • 还有其他线程属性,请参考这个文档

pthread_join调用注意事项

  • 调用该系统调用会使传入的线程拥有分离属性,如果该线程已经处于分离的状态,那么调用就会失败。
  • 调用该系统调用的线程会一直阻塞,直到指定的线程(用线程ID来标识)调用pthread_exit或者调用pthread_cancel(传入该线程的ID)从启动的线程回调函数中返回

下面的例子是一个非常好的例子,非常形象地解析Linux内核的线程管理机制的,首先我们创建了一个代表非负整数序列的结构体Seque,然后在1到200分成三个长度不同的数据区间,分别传递给三个线程。

#include 

输出结果:你觉得很奇怪是吗?为什么三个子线程的ID是一样的呢?

78073332433e497a526da50f1d7a81c0.png

我们分析一下这里的三个线程发生了什么事情,首先我们在for循环中分别使用pthread_create创先后创建了三个进程(注意:字眼是“先后”代表是有顺序)

  1. 第一个被创建的线程,系统从线程ID的编号空间中分配一个线程ID(十六进制)0x7f1657e2e700给tids[0],随后立即将该子线程传递给pthread_join处理,此时第一个线程创建之后,但未被pthread_join函数处理完之前,该线程的状态是“运行”了(在执行calc函数)。注意,这个phread_join是主线程调用的,因此主线程(main函数所在线程)进入“阻塞”状态,此时主线程只能等第一个子线程结束并且什么事都做不了,因此也无法创建剩下的两个子线程。
  2. 当第一个线程终止后,主线程从“阻塞”回到“运行”状态,而且在第一个子线程结束后,系统内核回收了第一个子线程ID:0x7f1657e2e700,并将该ID再“转租”给第二个子线程。因此第一个子线程和第二个子线程的线程ID是一样的。当主线程再次调用pthread_join后,那么主线程再次从“运行”状态切换到“阻塞”状态,一直等到第二个子线程执行完毕,才能接着创建第三个子线程。
  3. 同理,第三个子线程也和前面两个步骤的分析一样。

你认为这个分析不合理吗?我也查阅了gnu官网的相关文档说明https://www.gnu.org/software/libc/manual/html_node/Process-Identification.html

d35f9b0ba21ca84423df66f7e9ea89b0.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的线程无法执行接下来的其他指令。对应某一个用于创建其他子线程的线程(通常是主线程,这里用一个代号M称呼它)来说,线程M的函数上下文满足先是一个循环内先pthread_create创建线程,接着又调用pthread_join的情形,其实就是按先后顺序先创建的子线程先执行,在子线程未返回之前,线程M就等着瞎掰,线程M只有等到该子线程函数内部返回后,才能创建并执行接下的子线程。

此时,你应该深刻理解join这个线程术语的隐晦的含义:"意味着多个子线程任务要按照先后顺序排列顺序执行,并且一次只能执行一个线程任务(该任务运行中),后续子线程任务处于等待状态,因为他们压根就没有被创建。处于阻塞状态的是分派(创建线程)任务的包工头(即线程M)"。这也意味着所谓的线程同步.

试问上面的示例跟一个单线程的同步调用没什么区别!~因为主线程的间隔性的堵塞,令到多个子线程没法连续创建和并行执行。因此,我们使用在多线程编程中,要谨慎使用phread_join调用,我们应该结合我们具体的上下问逻辑来合理使用phread_join系统调用.

那么线程同步的适用场合是什么呢?这个由传入被线程处理的具体任务而定,一些不可分割且有严格先后顺序的任务,就是不能同时分割成多个子任务独立执行的,只能使用线程同步。

Join vs Detach

这是本篇的主题,我们之前本篇开头已经说了pthread_detach能够线程属性改变为分离状态,在退出时自动释放其分配的资源。 不需要其他线程连接它。 但是默认情况下所有线程都是可连接(Joinable),因此要分离一个线程,我们需要使用指定的线程ID传递给pthread_detach系统调用,并且在主线程中调用它,我们上面的main线程,将pthread_joint替换成pthread_detach即可,如下代码

int 

我们编译后尝试运行一下,从下面的输出,三个线程ID是不一样的证明子线程可以并行执行行了,但是主线程是提前return了,因为整个程序计算的整数序列的输出结果是不对的。

002293dff14c0c07fe1b53c6102c8974.png

那么究竟有没有方法可以保证所以子线程可以并发运行,同时主线程在确认所有子线程返回计算结果后,主线程再执行剩余的操作再返回?你可能会想到pthread_exit调用吗!非常抱歉pthread_exit在这里不适用,因为pthread_exit之后的所有语句都是不会再执行的,当然包括主线程剩余的代码。

我们先将这个问题,留给下一篇文章来解决.后续继续更新....

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值