Linux线程API详解(下)

问题

一个线程执行后申请了资源,中途又不得不提前结束,该怎么办?

线程清理函数

void pthread_cleanup_push(void (*start_routine) (void*), void* arg);

void pthread_cleanup_pop(int execute);

pthread_cleanup_push:

  • 注册线程 "被迫" 结束时,需要执行的函数 (资源释放可在函数中完成)

pthread_cleanup_pop:

  • "弹出" 之前注册的函数,根据参数决定是否执行函数
  • "弹出操作" 必须位于 return 语句之前

下面的程序输出什么?为什么?

何时会触发注册的清理函数?

pthread_exit() 与 pthread_cancel() 总是会触发清理函数执行

pthread_cleanup_pop() 的参数非零时,触发一个清理函数执行

通过 return 返回,未必会触发清理函数执行

清理函数触发实验

test1.c

#include <stdio.h>
#include <pthread.h>

void clean_test(void* arg)
{
	printf("clean_test : %lld\n", (long long)arg);
}

void foo(void)
{
	printf("I'm foo()!\n");
	pthread_exit(0);
}

void* pthread_entry(void* arg)
{
	pthread_cleanup_push(clean_test, (void*)111);
	
	foo();
	
	pthread_cleanup_pop(1);
	
	return NULL;
}

int main(int argc, char* argv[])
{
	pthread_t tid = {0};
	
	pthread_create(&tid, NULL, pthread_entry, NULL);
	
	pthread_join(tid, NULL);
	
	return 0;
}

第 17 行,通过 pthread_cleanup_push() 设置线程退出后的清理函数

第 21 行,pthread_cleanup_pop() 的参数为 1,表示线程退出后,会调用线程清理函数 clean_test()

子线程调用 foo() 后,会提前退出

程序允许结果如下图所示:

清理函数被执行了

将第 21 行,pthread_cleanup_pop() 的参数改为 0,查看程序运行结果:

清理函数被执行了

test2.c

#include <stdio.h>
#include <pthread.h>

void clean_test(void* arg)
{
	printf("clean_test : %lld\n", (long long)arg);
}

void foo(void)
{
	printf("I'm foo()!\n");
	// pthread_exit(0);
}

void* pthread_entry(void* arg)
{
	pthread_cleanup_push(clean_test, (void*)111);
	
	foo();
	
	pthread_cleanup_pop(1);
	
	return NULL;
}

int main(int argc, char* argv[])
{
	pthread_t tid = {0};
	
	pthread_create(&tid, NULL, pthread_entry, NULL);
	
	pthread_join(tid, NULL);
	
	return 0;
}

将 foo() 中的 pthread_exit() 去除

程序运行结果如下图所示:

清理函数被执行了

将第 21 行,pthread_cleanup_pop() 的参数改为 0,查看程序运行结果:

清理函数没有被调用

将 23 行 return NULL 改为 pthread_exit(0),查看程序运行结果:

清理函数没有被调用

test3.c

#include <stdio.h>
#include <pthread.h>

void clean_test(void* arg)
{
	printf("clean_test : %lld\n", (long long)arg);
}

void foo(void)
{
	printf("I'm foo()!\n");
	// pthread_exit(0);
}

void* pthread_entry(void* arg)
{
	pthread_cleanup_push(clean_test, (void*)111);
	
	foo();
	
	return NULL;
	
	pthread_cleanup_pop(1);
	
}

int main(int argc, char* argv[])
{
	pthread_t tid = {0};
	
	pthread_create(&tid, NULL, pthread_entry, NULL);
	
	pthread_join(tid, NULL);
	
	return 0;
}

程序运行结果如下图所示:

清理函数没有被调用

结论:当线程注册了清理函数后,在 pthread_cleanup_pop() 前调用 pthread_exit(),则一定会触发清理函数的调用;若pthread_cleanup_pop() 的参数为 1,则也会触发清理函数的调用;在 pthread_cleanup_pop()  前 return 不会触发清理函数的调用,return 和清理函数没有关联

 下面的程序输出什么?为什么?

注意事项

Linux 中 "线程清理函数" 的本质是宏定义 (不是函数)

pthread_cleanup_push() 和 pthread_cleanup_pop() 之间构成内部作用域

pthread_cleanup_push() 定义中包含一个 "多余的" {

pthread_cleanup_pop() 定义中包含一个 "多余的" }

线程清理函数本质实验

test4.c

#include <stdio.h>
#include <pthread.h>

void clean_test(void* arg)
{
	printf("clean_test : %lld\n", (long long)arg);
}

void foo(void)
{
	printf("I'm foo()!\n");
}

void* pthread_entry(void* arg)
{
	pthread_cleanup_push(clean_test, (void*)111);
	
	int i = 0;
	
	foo();
	
	pthread_cleanup_pop(0);
	
	for(i = 0; i < 10; i++)
	{
		printf("i = %d\n", i);
	}
	
	return NULL;
}

int main(int argc, char* argv[])
{
	pthread_t tid = {0};
	
	pthread_create(&tid, NULL, pthread_entry, NULL);
	
	pthread_join(tid, NULL);
	
	return 0;
}

编译上面的程序,查看编译结果

编译未通过,提示变量 i 未定义,但是我们明明在线程函数体中定义了 i,这是因为pthread_cleanup_push() 和 pthread_cleanup_pop() 是宏定义而不是函数,

pthread_cleanup_push() 定义中包含一个 "多余的" {,pthread_cleanup_pop() 定义中包含一个 "多余的" },这样我们定义的 i 的作用域就不在线程函数体中了

思考

如果主线程调用了 pthread_exit(0) 会发生什么?

下面的程序输出什么?为什么?

线程深度实验

test5.c

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* pthread_entry(void* arg)
{
	for(int i = 0; i < 20; i++)
	{
		printf("I'm foo()!\n");
		sleep(1);
	}
	
	return NULL;
}

int main(int argc, char* argv[])
{
	pthread_t tid = {0};
	
	pthread_create(&tid, NULL, pthread_entry, NULL);
	
	pthread_exit(NULL);
}

第 22 行,主线程通过 pthread_exit() 退出

程序运行结果如下图所示:

主线程退出后,子线程还在继续运行

我们使用 ps aux 命令查看下进程的状态

进程的状态为 Zl+,表示当前进程的状态为僵尸态,有多个线程,位于前台进程组中

当主线程通过 pthread_exit() 退出后,如果还有子线程在运行,则进程变成僵尸态

将程序第 27 行,改为 return 0

程序运行结果如下图所示:

主线程通过 return 退出,进程直接结束了

将程序第 27 行,改为 exit(0)

程序运行结果如下图所示:

主线程通过 exit() 退出,进程直接结束了

pthread_exit() VS exit()

pthread_exit() => 结束当前执行流,仅释放线程固定资源

  • 如果调用线程是主线程,则:
    • 单线程 => 线程执行流结束,进程立即结束
    • 多线程 => 主线程执行流结束,进程进入僵尸状态,其他线程继续执行

exit() => 结束当前进程,释放所有资源

一些推论

Linux 中进程的结束标志是 主线程 是否执行结束

  • 当主线程执行结束 (exit()) 进程结束,所有资源被释放
  • 主线程调用 pthread_exit() 进程进入僵尸状态 (子线程执行结束则进程结束)

main() 中执行 return 语句,意味着主线程执行结束,因此进程结束

  • 在应用开发过程中,应避免在主线程中调用 pthread_exit()

pthread_create() VS fork()

再论 fork():

  • 创建子进程,然而不会复制父进程空间中的数据 
  • 子进程直接使用父进程空间 (没有完整独立的进程空间)
  • 子进程可以使用父进程的数据 (堆,栈,全局)
  • 子进程不能从创建点 return 返回

pthread_create() VS vfork()

vfork() 进程与线程的不同

vfork() 进程只是一个新的执行流 (无任何附带资源的执行流)

线程有自己的专用资源 (如:栈) 同时共享进程全局资源

vfork() 不能和父进程同时执行 (父进程等待子进程结束)

子线程 和 主线程 同时执行,可同时使用进程全局资源

  • 17
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值