线程和进程一样,子线程退出的时候其内核资源主要由主线程回收,线程库中提供的线程回收函叫做 pthread_join(),这个函数是一个阻塞函数,如果还有子线程在运行,调用该函数就会阻塞,子线程退出函数解除阻塞进行资源的回收,函数被调用一次,只能回收一个子线程,如果有多个子线程则需要循环进行回收。
另外通过线程回收函数还可以获取到子线程退出时传递出来的数据,函数原型如下:
#include <pthread.h> // 这是一个阻塞函数, 子线程在运行这个函数就阻塞 // 子线程退出, 函数解除阻塞, 回收对应的子线程资源, 类似于回收进程使用的函数 wait() int pthread_join(pthread_t thread, void **retval);
参数:
thread: 要被回收的子线程的线程 ID retval: 二级指针,指向一级指针的地址,是一个传出参数,这个地址中存储了 pthread_exit () 传递出的数据,如果不需要这个参数,可以指定为 NULL 返回值:线程回收成功返回 0,回收失败返回错误号。
1. 回收子线程数据
在子线程退出的时候可以使用 pthread_exit() 的参数将数据传出,在回收这个子线程的时候可以通过 phread_join() 的第二个参数来接收子线程传递出的数据。接收数据有很多种处理方式,下面来列举几种:
1.1 使用子线程栈
通过函数 pthread_exit(void retval); 可以得知,子线程退出的时候,需要将数据记录到一块内存中,通过参数传出的是存储数据的内存的地址,而不是具体数据,由因为参数是 void 类型,所有这个万能指针可以指向任意类型的内存地址。先来看第一种方式,将子线程退出数据保存在子线程自己的栈区:
// pthread_join.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> // 定义结构 struct Persion { int id; char name[36]; int age; }; // 子线程的处理代码 void* working(void* arg) { printf("我是子线程, 线程ID: %ld\n", pthread_self()); for(int i=0; i<9; ++i) { printf("child == i: = %d\n", i); if(i == 6) { struct Persion p; p.age =12; strcpy(p.name, "tom"); p.id = 100; // 该函数的参数将这个地址传递给了主线程的pthread_join() pthread_exit(&p); } } return NULL; // 代码执行不到这个位置就退出了 } int main() { // 1. 创建一个子线程 pthread_t tid; pthread_create(&tid, NULL, working, NULL); printf("子线程创建成功, 线程ID: %ld\n", tid); // 2. 子线程不会执行下边的代码, 主线程执行 printf("我是主线程, 线程ID: %ld\n", pthread_self()); for(int i=0; i<3; ++i) { printf("i = %d\n", i); } // 阻塞等待子线程退出 void* ptr = NULL; // ptr是一个传出参数, 在函数内部让这个指针指向一块有效内存 // 这个内存地址就是pthread_exit() 参数指向的内存 pthread_join(tid, &ptr); // 打印信息 struct Persion* pp = (struct Persion*)ptr; printf("子线程返回数据: name: %s, age: %d, id: %d\n", pp->name, pp->age, pp->id); printf("子线程资源被成功回收...\n"); return 0; }
编译输出:
NOTE:
通过打印的日志可以发现,在主线程中没有没有得到子线程返回的数据信息,具体原因是这样的:
如果多个线程共用同一个虚拟地址空间,每个线程在栈区都有一块属于自己的内存,相当于栈区被这几个线程平分了,当线程退出,线程在栈区的内存也就被回收了,因此随着子线程的退出,写入到栈区的数据也就被释放了。
1.2 使用全局变量
位于同一虚拟地址空间中的线程,虽然不能共享栈区数据,但是可以共享全局数据区和堆区数据,因此在子线程退出的时候可以将传出数据存储到全局变量、静态变量或者堆内存中。在下面的例子中将数据存储到了全局变量中:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> // 定义结构 struct Persion { int id; char name[36]; int age; }; struct Persion p; // 定义全局变量 // 子线程的处理代码 void* working(void* arg) { printf("我是子线程, 线程ID: %ld\n", pthread_self()); for(int i=0; i<9; ++i) { printf("child == i: = %d\n", i); if(i == 6) { // 使用全局变量 p.age =12; strcpy(p.name, "tom"); p.id = 100; // 该函数的参数将这个地址传递给了主线程的pthread_join() pthread_exit(&p); } } return NULL; } int main() { // 1. 创建一个子线程 pthread_t tid; pthread_create(&tid, NULL, working, NULL); printf("子线程创建成功, 线程ID: %ld\n", tid); // 2. 子线程不会执行下边的代码, 主线程执行 printf("我是主线程, 线程ID: %ld\n", pthread_self()); for(int i=0; i<3; ++i) { printf("i = %d\n", i); } // 阻塞等待子线程退出 void* ptr = NULL; // ptr是一个传出参数, 在函数内部让这个指针指向一块有效内存 // 这个内存地址就是pthread_exit() 参数指向的内存 pthread_join(tid, &ptr); // 打印信息 struct Persion* pp = (struct Persion*)ptr; printf("name: %s, age: %d, id: %d\n", pp->name, pp->age, pp->id); printf("子线程资源被成功回收...\n"); return 0; }
编译输出:
可以看到通过子线程通过全局变量确实将数据传递到了主线程
1.3 使用主线程栈
虽然每个线程都有属于自己的栈区空间,但是位于同一个地址空间的多个线程是可以相互访问对方的栈空间上的数据的。由于很多情况下还需要在主线程中回收子线程资源,所以主线程一般都是最后退出,基于这个原因在下面的程序中将子线程返回的数据保存到了主线程的栈区内存中:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h> // 定义结构 struct Persion { int id; char name[36]; int age; }; // 子线程的处理代码 void* working(void* arg) { struct Persion* p = (struct Persion*)arg; printf("我是子线程, 线程ID: %ld\n", pthread_self()); for(int i=0; i<9; ++i) { printf("child == i: = %d\n", i); if(i == 6) { // 使用主线程的栈内存 p->age =12; strcpy(p->name, "tom"); p->id = 100; // 该函数的参数将这个地址传递给了主线程的pthread_join() pthread_exit(p); } } return NULL; } int main() { // 1. 创建一个子线程 pthread_t tid; struct Persion p; // 主线程的栈内存传递给子线程 pthread_create(&tid, NULL, working, &p); printf("子线程创建成功, 线程ID: %ld\n", tid); // 2. 子线程不会执行下边的代码, 主线程执行 printf("我是主线程, 线程ID: %ld\n", pthread_self()); for(int i=0; i<3; ++i) { printf("i = %d\n", i); } // 阻塞等待子线程退出 void* ptr = NULL; // ptr是一个传出参数, 在函数内部让这个指针指向一块有效内存 // 这个内存地址就是pthread_exit() 参数指向的内存 pthread_join(tid, &ptr); // 打印信息 printf("name: %s, age: %d, id: %d\n", p.name, p.age, p.id); printf("子线程资源被成功回收...\n"); return 0; }
编译输出:
在上面的程序中,调用 pthread_create() 创建子线程,并将主线程中栈空间变量 p 的地址传递到了子线程中,在子线程中将要传递出的数据写入到了这块内存中。也就是说在程序的 main() 函数中,通过指针变量 ptr 或者通过结构体变量 p 都可以读出子线程传出的数据。