Linux线程(五)分离线程与注册线程清理函数详解

1.分离线程

默认情况下,当线程终止时,其它线程可以通过调用 pthread_join()获取其返回状态、回收线程资源,有时,程序员并不关系线程的返回状态,只是希望系统在线程终止时能够自动回收线程资源并将其移除。在这种情况下,可以调用 pthread_detach()将指定线程进行分离,也就是分离线程,pthread_detach()函数原型如下所示:

#include <pthread.h>

int pthread_detach(pthread_t thread);

使用该函数需要包含头文件<pthread.h>,参数 thread 指定需要分离的线程,函数 pthread_detach()调用成功将返回 0;失败将返回一个错误码。

一个线程既可以将另一个线程分离,同时也可以将自己分离,譬如:

pthread_detach(pthread_self());

一旦线程处于分离状态,就不能再使用 pthread_join()来获取其终止状态,此过程是不可逆的,一旦处于分离状态之后便不能再恢复到之前的状态。处于分离状态的线程,当其终止后,能够自动回收线程资源。

使用示例

//示例代码 11.7.1 pthread_detach()分离线程使用示例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>

static void *new_thread_start(void *arg) {
	 int ret;

	 /* 自行分离 */
	 ret = pthread_detach(pthread_self());
	 if (ret) {
		 fprintf(stderr, "pthread_detach error: %s\\n", strerror(ret));
		 return NULL;
	 }
	 printf("新线程 start\\n");
	 sleep(2); //休眠 2 秒钟
	 printf("新线程 end\\n");
	 pthread_exit(NULL);
	}

int main(void) {
	 pthread_t tid;
	 int ret;

	 /* 创建新线程 */
	 ret = pthread_create(&tid, NULL, new_thread_start, NULL);
	 if (ret) {
		 fprintf(stderr, "pthread_create error: %s\\n", strerror(ret));
		 exit(-1);
	 }
	 sleep(1); //休眠 1 秒钟

	 /* 等待新线程终止 */
	 ret = pthread_join(tid, NULL);
	 if (ret)
		 fprintf(stderr, "pthread_join error: %s\\n", strerror(ret));
		 pthread_exit(NULL);
}

示例代码中,主线程创建新的线程之后,休眠 1 秒钟,调用 pthread_join()等待新线程终止;新线程调用pthread_detach(pthread_self())将自己分离,休眠 2 秒钟之后 pthread_exit()退出线程;主线程休眠 1 秒钟是能够确保调用 pthread_join()函数时新线程已经将自己分离了,所以按照上面的介绍可知,此时主线程调用pthread_join()必然会失败,测试结果如下:

打印结果正如我们所料,主线程调用 pthread_join()确实会出错,错误提示为“Invalid argument”。

2.注册线程清理函数

学习了 atexit()函数,使用 atexit()函数注册进程终止处理函数,当进程调用 exit()退出时就会

执行进程终止处理函数;其实,当线程退出时也可以这样做,当线程终止退出时,去执行这样的处理函数,我们把这个称为线程清理函数(thread cleanup handler)。

与进程不同,一个线程可以注册多个清理函数,这些清理函数记录在栈中,每个线程都可以拥有一个清理函数栈,栈是一种先进后出的数据结构,也就是说它们的执行顺序与注册(添加)顺序相反,当执行完所有清理函数后,线程终止。

线程通过函数 pthread_cleanup_push()和 pthread_cleanup_pop()分别负责向调用线程的清理函数栈中添加和移除清理函数,函数原型如下所示:

#include <pthread.h>

void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);

使用这些函数需要包含头文件<pthread.h>。

调用 pthread_cleanup_push()向清理函数栈中添加一个清理函数,第一个参数 routine 是一个函数指针,指向一个需要添加的清理函数,routine()函数无返回值,只有一个 void *类型参数;第二个参数 arg,当调用清理函数 routine()时,将 arg 作为 routine()函数的参数。

既然有添加,自然就会伴随着删除,就好比对应入栈和出栈,调用函数 pthread_cleanup_pop()可以将清理函数栈中最顶层(也就是最后添加的函数,最后入栈)的函数移除。

当线程执行以下动作时,清理函数栈中的清理函数才会被执行:

  • 线程调用 pthread_exit()退出时;
  • 线程响应取消请求时;
  • 用非 0 参数调用 pthread_cleanup_pop()

除了以上三种情况之外,其它方式终止线程将不会执行线程清理函数,譬如在线程 start 函数中执行return 语句退出时不会执行清理函数。

函数 pthread_cleanup_pop()的 execute 参数,可以取值为 0,也可以为非 0;如果为 0,清理函数不会被调用,只是将清理函数栈中最顶层的函数移除;如果参数 execute 为非 0,则除了将清理函数栈中最顶层的函数移除之外,还会该清理函数。

尽管上面我们将 pthread_cleanup_push()和 pthread_cleanup_pop()称之为函数,但它们是通过宏来实现,可展开为分别由{和}所包裹的语句序列,所以必须在与线程相同的作用域中以匹配对的形式使用,必须一一对应着来使用,譬如:

pthread_cleanup_push(cleanup, NULL);
pthread_cleanup_push(cleanup, NULL);
pthread_cleanup_push(cleanup, NULL);
......
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
thread_cleanup_pop(0);

否则会编译报错,如下所示:

使用示例

示例代码 11.8.1 给出了一个使用线程清理函数的例子,虽然例子并没有什么实际作用,当它描述了其中所涉及到的清理机制。

//示例代码 11.8.1 pthread_cleanup_push()注册线程清理函数
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>

static void cleanup(void *arg) {
 printf("cleanup: %s\\n", (char *)arg);
}

static void *new_thread_start(void *arg) {
 printf("新线程--start run\\n");
 pthread_cleanup_push(cleanup, "第 1 次调用");
 pthread_cleanup_push(cleanup, "第 2 次调用");
 pthread_cleanup_push(cleanup, "第 3 次调用");
 sleep(2);
 pthread_exit((void *)0); //线程终止
 /* 为了与 pthread_cleanup_push 配对,不添加程序编译会通不过 */
 pthread_cleanup_pop(0);
 pthread_cleanup_pop(0);
 pthread_cleanup_pop(0);
}
int main(void) {
 pthread_t tid;
 void *tret;
 int ret;
 /* 创建新线程 */
 ret = pthread_create(&tid, NULL, new_thread_start, NULL);
 if (ret) {
 fprintf(stderr, "pthread_create error: %s\\n", strerror(ret));
 exit(-1);
 }
 /* 等待新线程终止 */
 ret = pthread_join(tid, &tret);
 if (ret) {
 fprintf(stderr, "pthread_join error: %s\\n", strerror(ret));
 exit(-1);
 }
 printf("新线程终止, code=%ld\\n", (long)tret);
 exit(0);
}

主线程创建新线程之后,调用 pthread_join()等待新线程终止;新线程调用 pthread_cleanup_push()函数添加线程清理函数,调用了三次,但每次添加的都是同一个函数,只是传入的参数不同;清理函数添加完成,休眠一段时间之后,调用 pthread_exit()退出。之后还调用了 3 次 pthread_cleanup_pop(),在这里的目的仅仅只是为了与 pthread_cleanup_push()配对使用,否则编译不通过。接下来编译运行:

从打印结果可知,先添加到线程清理函数栈中的函数会后被执行,添加顺序与执行顺序相反。

将新线程中调用的 pthread_exit()替换为 return,在进行测试,发现并不会执行清理函数。

有时在线程功能设计中,线程清理函数并不一定需要在线程退出时才执行,譬如当完成某一个步骤之后,就需要执行线程清理函数,此时我们可以调用 pthread_cleanup_pop()并传入非 0 参数,来手动执行线程清理函数,示例代码如下所示:

//示例代码 11.8.2 手动执行线程清理函数
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>

static void cleanup(void *arg) {
 printf("cleanup: %s\\n", (char *)arg);
}

static void *new_thread_start(void *arg) {
		 printf("新线程--start run\\n");
		 pthread_cleanup_push(cleanup, "第 1 次调用");
		 pthread_cleanup_push(cleanup, "第 2 次调用");
		 pthread_cleanup_push(cleanup, "第 3 次调用");
		 pthread_cleanup_pop(1); //执行最顶层的清理函数
		 printf("~~~~~~~~~~~~~~~~~\\n");
		 sleep(2);
		 pthread_exit((void *)0); //线程终止
		 /* 为了与 pthread_cleanup_push 配对 */
		 pthread_cleanup_pop(0);
		 pthread_cleanup_pop(0);
}

int main(void) {
		 pthread_t tid;
		 void *tret;
		 int ret;

		 /* 创建新线程 */
		 ret = pthread_create(&tid, NULL, new_thread_start, NULL);
		 if (ret) {
				 fprintf(stderr, "pthread_create error: %s\\n", strerror(ret));
				 exit(-1);
		 }

		 /* 等待新线程终止 */
		 ret = pthread_join(tid, &tret);
		 if (ret) {
				 fprintf(stderr, "pthread_join error: %s\\n", strerror(ret));
				 exit(-1);
		 }

		 printf("新线程终止, code=%ld\\n", (long)tret);
		 exit(0);
}

上述代码中,在新线程调用 pthread_exit()之前,先调用 pthread_cleanup_pop(1)手动运行了最顶层的清理

函数,并将其从栈中移除,测试结果:

从打印结果可知,调用 pthread_cleanup_pop(1)执行了最后一次注册的清理函数,调用 pthread_exit()退出线程时执行了 2 次清理函数,因为前面调用 pthread_cleanup_pop()已经将顶层的清理函数移除栈中了,自然在退出时就不会再执行了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dola_Pan

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值