pthread_join用于阻塞等待某线程的退出,获取其返回值。 任何线程都可以pthread_join等待其他线程,而不一定非要主线程等待子线程。
pthread_join通常用于控制各线程的退出时序,便于资源的有序释放。 有时会遇到一些C模块,在重启或者退出时出Core,尽管并不影响线上服务, 但出Core会触发报警,多多少少让人很不爽。往往都是因为资源的释放时序有问题导致的, 比如一个线程申请的内存,传给另一个线程在使用,如果申请内存的线程提前退出, 若不释放内存,这块内存就泄漏了,若释放内存,可能运算线程还没有使用完,就会导致出Core。 此时有两种处理方案,其一,反正程序都要退出了,内存泄漏就泄漏吧,让操作系统去回收吧, 这样,只需要主线程pthread_join所有线程,内存全都不释放,这不失一种省时省力的好方法; 其二,按照线程之间的内存依赖关系,退出时逐层pthread_join,并释放内存。 其实还有一种比较恶心的方法,就是负责释放内存的线程,先sleep个3秒钟,即等其他处理线程肯定处理完了,再退出, 曾经就遇到过,有程序退出时,有时Core,有时不Core,在某个地方sleep一会,就不再Core了,但明显这种方法太trick了。
举个例子,一个复杂的模块包含查询和更新存储的数据,当然查询的时候包含了复杂的计算,称作计算线程, 共是9个线程,主线程创建了一个epoll线程,负责监听查询端口,建立连接,接收查询请求, 又创建了4个计算线程,负责处理查询请求,又有一个poll线程,负责监听更新端口,接收更新请求, 又有一个更新线程,负责处理更新请求,对了,还有一个Timer线程,负责进行一些定时计算, 比如定时清理脏数据什么的。在这个例子中,查询处理线程和epoll线程是消费者和生产者关系, 是有内存传递的,更新处理线程和poll线程也一样,而定时线程和更新线程以及查询线程是有读写锁共享关系的。 这样在程序退出时,需要epoll线程pthread_join四个查询处理线程,poll线程pthread_join更新处理线程, 主线程pthread_join epoll线程、poll线程和Timer线程。在pthread_join之后,进行内存的释放操作。
pthread_join任意一个线程,会遇到以下几种特殊情形:
下面的代码会出Core,它模拟了一个线程被多个线程pthread_join的情况:
#include <iostream>
#include <stdio.h>
#include <sys/time.h>
#include <pthread.h>
#include <stdint.h>
#include <queue>
#include <unistd.h>
using namespace std;
class Task
{
public:
int init()
{
for (int i=0; i<10; ++i) {
pthread_create(th+i, NULL, handle, this);
}
pthread_create(th+10, NULL, handle2, this);
return 0;
}
int join()
{
for (int i=0; i<10; ++i) {
pthread_join(th[i], NULL);
}
return 0;
}
void process()
{
for (int i = 0; i < 10; ++i) {
printf("hahha\n");
sleep(1);
printf("dede\n");
}
}
void process2()
{
printf("thread join begin\n");
for (int i=0; i<10; ++i) {
pthread_join(th[i], NULL);
}
printf("thread join end\n");
}
private:
static void *handle(void *arg)
{
((Task*)arg)->process();
return NULL;
}
static void *handle2(void *arg)
{
((Task*)arg)->process2();
return NULL;
}
private:
pthread_t th[11];
};
int main()
{
Task task;
task.init();
task.join();
return 0;
}