LV025-线程取消与清理
本文主要是线程——线程取消与清理的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
一、线程取消
1. 取消函数
1.1 pthread_cancel()
1.1.1 函数说明
在 linux 下可以使用 man pthread_cancel 命令查看该函数的帮助手册。
/* Compile and link with -pthread. */
#include <pthread.h>
int pthread_cancel(pthread_t thread);【函数说明】该函数向一个指定的线程(包括调用此函数的线程)发送取消请求。
【函数参数】
- thread : pthread_t 类型,参数 thread 指定需要取消的目标线程的线程 ID 。
【返回值】 int 类型,成功返回 0 ,失败将返回错误码。
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#include <pthread.h>
/* 至少应该有的语句 */
pthread_cancel(tid);
pthread_cancel(pthread_self());/* 向自己发送请求*/【注意事项】
(1)发出取消请求之后,函数 pthread_cancel() 立即返回,不会等待目标线程的退出。默认情况下,目标线程也会立刻退出,其行为表现为如同调用了参数为 PTHREAD_CANCELED (其实就是 (void *)-1 )的 pthread_exit() 函数,但是,线程可以设置自己不被取消或者控制如何被取消后边会介绍,所以 pthread_cancel() 并不会等待线程终止,仅仅只是提出请求。
(2)线程也可以向自己发送取消请求。
pthread_cancel(pthread_self());1.1.2 使用实例
#include <stdio.h>
#include <pthread.h>/* pthread_create pthread_exit pthread_cancel */
#include <unistd.h> /* sleep */
#include <string.h> /* strerror */
#define macro 1 /* 决定主线程是否发出取消请求 */
void *threadCancel(void *arg);
int main(int argc, char *argv[])
{
int i = 0;
int ret;
pthread_t tid;
void *retv;
/* 1. 创建一个线程 */
ret = pthread_create(&tid, NULL, (void *)threadCancel, NULL);
printf("This is main thread,ret=%d,tid=%lu\n", ret, tid);
for(i = 0; i < 2; i++)
{
sleep(1);/* 停留2s后取消创建的新线程*/
printf("main thread running!i = %d\n", i);
}
/* 2. 取消线程 */
printf("cancel thread!\n----------------\n");
#if macro == 1
pthread_cancel(tid);
#endif
/* 3. 回收线程 */
ret = pthread_join(tid, &retv);
if(ret != 0)
printf("pthread_join error: %s\n", strerror(ret));
/* 这一句将会导致一个段错误 */
printf("thread ret=%s\n", (char*)retv);
return 0;
}
void *threadCancel(void *arg)
{
int i = 0;
printf("This is a new thread!\n");
for(i = 0; i < 5; i++)
{
printf("new thread running! i = %d\n", i);
sleep(1);
}
/* 由于线程取消,下边这句永远不会执行,所以就无返回值,于是主线程中的回收线程
函数就会出现内存访问的错误 */
printf("new thread exit!\n");
pthread_exit("new thread return");
}程序中的宏 macro 用于决定主线程是否发出取消请求。
- 若 macro 为1 ,则主线程进入 for 循环休眠 2s 后发出取消请求。
在终端执行以下命令编译程序:
gcc test.c -Wall -l pthread # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
This is main thread,ret=0,tid=140496116229696
This is a new thread!
new thread running! i = 0
main thread running!i = 0
new thread running! i = 1
main thread running!i = 1
cancel thread!
----------------
new thread running! i = 2
段错误 (核心已转储)此种情况下,主线程在休眠 2s 后,向新线程发送取消请求,当新线程执行完循环内的 printf 后直接取消线程,所以后边的 pthread_exit() 函数是不会执行的,也就不存在退出码,这也意味着主线程中的线程回收函数 pthread_join() 根本就接收不到 retval 参数,而这又是一个指针变量,这个指针就成为了一个空指针,主线程中访问了一个空指针,自然会出现段错误。
- 若 macro 为 0 ,则主线程不会发出线程取消请求。
在终端执行以下命令编译程序:
gcc test.c -Wall -l pthread # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
This is main thread,ret=0,tid=140382870578752
This is a new thread!
new thread running! i = 0
main thread running!i = 0
new thread running! i = 1
main thread running!i = 1
cancel thread!
----------------
new thread running! i = 2
new thread running! i = 3
new thread running! i = 4
new thread exit!
thread ret=new thread return此种情况为正常运行,主线程和新线程都正常运行完毕。
【注意事项】首先我们需要知道 printf 和 sleep 都属于 Cancellation points 也就是取消点,这个后边会有一节笔记专门记录这个这个的存在导致新线程中 sleep 语句和 printf 语句放置位置的不同就会有不同的情况,若 sleep 在前,当发出取消请求后, printf 不会再执行。
2. 取消状态及类型
2.1 pthread_setcancelstate()
2.1.1 函数说明
在 linux 下可以使用 man pthread_setcancelstate 命令查看该函数的帮助手册。
/* Compile and link with -pthread. */
#include <pthread.h>
int pthread_setcancelstate(int state, int *oldstate);【函数说明】设置调用该函数的线程的取消状态。
【函数参数】
- state : int 类型,参数 state 中给定的值将会被设置为调用线程的取消状态。state 可取的值如下:
| PTHREAD_CANCEL_ENABLE | 线程可以取消,这是新创建的线程取消性状态的默认值,所以新建线程以及主线程默认都是可以取消的。 |
| PTHREAD_CANCEL_DISABLE | 线程不可被取消,如果此类线程接收到取消请求,则会将请求挂起,直至线程的取消性状态变为PTHREAD_CANCEL_ENABLE。 |
【返回值】 int 类型,成功返回 0 ,失败将返回错误码。
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#include <pthread.h>
/* 至少应该有的语句 */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);/* 设置为不可被取消 */
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);/* 设置为可被取消 */【注意事项】 pthread_setcancelstate() 函数执行的设置取消性状态和获取旧状态操作,这两步是一个原子操作。
2.1.2 使用实例
#include <stdio.h>
#include <pthread.h>/* pthread_create pthread_exit pthread_cancel */
#include <unistd.h> /* sleep */
#include <string.h> /* strerror */
#define macro 1 /* 决定新线程中是否设置取消状态 */
void *threadCancel(void *arg);
int main(int argc, char *argv[])
{
int i = 0;
int ret;
pthread_t tid;
void *retv;
/* 1. 创建一个线程 */
ret = pthread_create(&tid, NULL, (void *)threadCancel, NULL);
printf("This is main thread,ret=%d,tid=%lu\n", ret, tid);
for(i = 0; i < 2; i++)
{
sleep(1);/* 停留5s后取消创建的新线程*/
printf("main thread running!i = %d\n", i);
}
/* 2. 取消线程 */
printf("cancel thread!\n----------------\n");
pthread_cancel(tid);
/* 3. 回收线程 */
ret = pthread_join(tid, &retv);
if(ret != 0)
printf("pthread_join error: %s\n", strerror(ret));
/* 这一句将会导致一个段错误 */
printf("thread ret=%s\n", (char*)retv);
return 0;
}
void *threadCancel(void *arg)
{
int i = 0;
#if macro == 1
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);/* 设置为不可被取消 */
#endif
printf("This is a new thread!\n");
for(i = 0; i < 5; i++)
{
sleep(1);
printf("new thread running!i = %d\n", i);
}
#if macro == 1
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);/* 设置为可被取消 */
#endif
printf("new thread exit!\n");
pthread_exit("new thread return");
}程序中主线程进入 for 循环休眠 2s 后发出取消请求,宏 macro 用于决定新线程中是否设置取消状态。
- 若 macro 为 1 ,则新线程会设置为不可取消, for 循环执行完毕后,线程才正常结束。
在终端执行以下命令编译程序:
gcc test.c -Wall -l pthread # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
This is main thread,ret=0,tid=139966405953088
This is a new thread!
main thread running!i = 0
new thread running!i = 0
main thread running!i = 1
cancel thread!
----------------
new thread running!i = 1
new thread running!i = 2
new thread running!i = 3
new thread running!i = 4
段错误 (核心已转储)此种情况下,主线程在休眠 2s 后,向新线程发送取消请求,由于新线程设置了自己不可被取消,所以会执行完自己内部的 for 循环,循环执行完毕后又设置自己为可取消,而下边刚好又出现了 printf 函数( Cancellation points ),此时线程直接取消,所以线程后边的线程退出函数 pthread_exit() 是不会执行的,也就不存在退出码,这也意味着主线程中的线程回收函数 pthread_join() 根本就接收不到 retval 参数,而这又是一个指针变量,这个指针就成为了一个空指针,主线程中访问了一个空指针,自然会出现段错误。
其实尝试一下去掉线程退出函数前边的 printf 语句,会发现线程会正常的退出,不会发生段错误。
- 若 macro 为 0 ,主线程进入 for 循环休眠 2s 后发出取消请求,新线程并未被设置为不可取消,所以遇到取消点会直接取消 。
在终端执行以下命令编译程序:
gcc test.c -Wall -l pthread # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
This is main thread,ret=0,tid=140574419174976
This is a new thread!
main thread running!i = 0
new thread running!i = 0
main thread running!i = 1
cancel thread!
----------------
new thread running!i = 1
段错误 (核心已转储)此种情况为主线程发送取消请求后,新线程中遇到 sleep 就直接取消了。
【注意事项】 printf 和 sleep 都属于 Cancellation points 也就是取消点。
2.2 pthread_setcanceltype()
2.2.1 函数说明
在 linux 下可以使用 man pthread_setcanceltype 命令查看该函数的帮助手册。
/* Compile and link with -pthread. */
#include <pthread.h>
int pthread_setcanceltype(int type, int *oldtype);【函数说明】设置调用该函数的线程的取消类型。
【函数参数】
- type : int 类型,参数 type 中给定的值将会被设置为调用线程的取消类型。type 可取的值如下:
| PTHREAD_CANCEL_DEFERRED | 取消请求到来时,线程还是继续运行,取消请求被挂起,直到线程到达某个取消点(cancellation point)为止,这是所有新建线程包括主线程默认的取消性类型。 |
| PTHREAD_CANCEL_ASYNCHRONOUS | 可能会在任何时间点取消线程,这种取消性类型应用场景很少。 【说明】通常是立即取消,但不一定,这里的立即只是说目标线程不用等执行到属于cancellation point的函数的时候才会取消,它会在获得调度之后立即取消,因为内核调度会有延时,所以并不能保证时间上的“立即”。 |
【返回值】 int 类型,成功返回 0 ,失败将返回错误码。
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#include <pthread.h>
/* 至少应该有的语句 */
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);/* 到达某个取消点取消 */
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);/* 立刻取消 */【注意事项】 pthread_setcanceltype() 函数执行的设置取消性类型和获取旧类型操作,这两步是一个原子操作。
2.2.2 使用实例
#include <stdio.h>
#include <pthread.h>/* pthread_create pthread_exit pthread_cancel pthread_testcancel pthread_self*/
#include <unistd.h> /* sleep */
#include <string.h>
#define macro 0
void *threadCancelType(void *arg);
int main(int argc, char *argv[])
{
int ret;
int i = 0;
pthread_t tid;
void *retv;
/* 1. 创建一个线程 */
ret = pthread_create(&tid, NULL, (void *)threadCancelType, NULL);
printf("This is main thread,ret=%d,tid=%lu\n", ret, tid);
for(i = 0; i < 2; i++)
{
sleep(1);
printf("main thread!i=%d\n", i);
}
/* 2. 申请取消线程 */
printf("----------------\ncancel thread!\n----------------\n");
pthread_cancel(tid);
/* 3. 回收线程,若取消成功,这里将会返回错误,然后打印信息 */
ret = pthread_join(tid, &retv);
if(ret != 0)
printf("pthread_join error: %s\n", strerror(ret));
printf("thread test end, code=%ld\n", (long)retv);
return 0;
}
void *threadCancelType(void *arg)
{
printf("This is a thread test!\n");
#if macro == 1
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);/* 到达取消点取消 */
#else
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);/* 立即取消 */
#endif
while(1);
return (void *)0;
}程序中的宏 macro 用于配置新线程的取消类型。
- 若 macro 为 1 ,则新线程取消类型为 PTHREAD_CANCEL_DEFERRED 。
在终端执行以下命令编译程序:
gcc test.c -Wall -l pthread # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
This is main thread,ret=0,tid=140369903380032
This is a thread test!
main thread!i=0
main thread!i=1
----------------
cancel thread!
----------------此种情况下,主线程在休眠 2s 后,向新线程发送取消请求,新线程设置为了到达取消点取消,下边有一个 while(1) 的死循环,且内部无取消点,这就意味着新线程不会结束,主线程也会阻塞等待新线程结束。可以重开一个终端使用如下命令查看线程:
ps -eLf|grep a.out- 若 macro 为 0 ,则新线程取消类型为 PTHREAD_CANCEL_ASYNCHRONOUS 。
在终端执行以下命令编译程序:
gcc test.c -Wall -l pthread # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
This is main thread,ret=0,tid=140581044491840
This is a thread test!
main thread!i=0
main thread!i=1
----------------
cancel thread!
----------------
thread test end, code=-1此种情况在新线程中设置了线程立即取消,所以即便没有遇到取消点,也会正常的结束线程,主线程也会正常执行完毕。
3. 取消点
3.1 取消点的概念
终于到了这一部分的笔记了,前边的关于线程取消的函数都是与取消点相关,现在就来了解一下这到底是什么吧。
前边知道若将线程的取消性类型设置为 PTHREAD_CANCEL_DEFERRED 时(线程可以取消状态下),收到其它线程发送过来的取消请求时,仅当线程抵达某个取消点时,取消请求才会起作用。
所谓取消点其实就是一系列函数,当执行到这些函数的时候,才会真正响应取消请求,这些函数就是取消点;在没有出现取消点时,取消请求是无法得到处理的。这是因为系统认为,即便收到了取消请求,但是没有到达取消点时,线程正在执行的工作是不能被停止的,正在执行关键代码,此时终止线程将可能会导致出现意想不到的异常发生。
3.2 取消点函数
关于取消点函数,我们在 Linux 下可以使用 man 7 pthreads 进行查看,取消点函数主要是帮助手册的 Cancellation points 部分。下边仅仅是罗列写这篇笔记时, Ubuntu21.04 中 man 手册上显示的取消点函数,只是便于自己查看,最好还是直接查 man 手册吧( POSIX.1-2001 and/or POSIX.1-2008 )。
- 在 POSIX.1-2001 and/or POSIX.1-2008 中必须是取消点的函数
| accept() | getpmsg() | open() | putmsg() | sem_wait() | system() |
| aio_suspend() | lockf() | openat() | putpmsg() | send() | tcdrain() |
| clock_nanosleep() | mq_receive() | pause() | pwrite() | sendmsg() | usleep() |
| close() | mq_send() | poll() | read() | sendto() | wait() |
| connect() | mq_timedreceive() | pread() | readv() | sigpause() | waitid() |
| creat() | mq_timedsend() | pselect() | recv() | sigsuspend() | waitpid() |
| fcntl() | msgrcv() | pthread_cond_timedwait() | recvfrom() | sigtimedwait() | write() |
| fdatasync() | msgsnd() | pthread_cond_wait() | recvmsg() | sigwait() | writev() |
| fsync() | msync() | pthread_join() | select() | sigwaitinfo() | |
| getmsg() | nanosleep() | pthread_testcancel() | sem_timedwait() | sleep() |
(1)以下函数有备注,上边为了不占篇幅,将备注删去了
fcntl() F_SETLKW
lockf() F_LOCK
openat() [Added in POSIX.1-2008]
sigpause() [POSIX.1-2001 only (moves to "may" list in POSIX.1-2008)]
usleep() [POSIX.1-2001 only (function removed in POSIX.1-2008)](2)还有一部分函数在 POSIX.1-2001 and/or POSIX.1-2008 可能是取消点函数,这些函数较多吗,这里就不写了,可以在 Ubuntu 中使用 man 7 pthreads 查看 Cancellation points 部分。
3.3 取消点函数执行嘛?
这个问题嘛,仅仅是记录自己的一个测试例程,上边我们知道 printf 函数就属于取消点函数,所以我们可以使用该函数进行一个测试。
#include <stdio.h>
#include <pthread.h>/* pthread_create pthread_exit pthread_cancel pthread_testcancel pthread_self*/
#include <unistd.h> /* sleep */
#include <string.h>
#define macro 0
void *threadCancelType(void *arg);
int main(int argc, char *argv[])
{
int ret;
int i = 0;
pthread_t tid;
void *retv;
/* 1. 创建一个线程 */
ret = pthread_create(&tid, NULL, (void *)threadCancelType, NULL);
printf("This is main thread,ret=%d,tid=%lu\n", ret, tid);
for(i = 0; i < 2; i++)
{
sleep(1);
printf("main thread!i=%d\n", i);
}
/* 2. 申请取消线程 */
printf("----------------\ncancel thread!\n----------------\n");
pthread_cancel(tid);
/* 3. 回收线程,若取消成功,这里将会返回错误,然后打印信息 */
ret = pthread_join(tid, &retv);
if(ret != 0)
printf("pthread_join error: %s\n", strerror(ret));
printf("thread test end, code=%ld\n", (long)retv);
return 0;
}
void *threadCancelType(void *arg)
{
int i = 0;
printf("This is a thread test!\n");
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL); /* 设置为到取消点取消 */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);/* 设置为不可被取消 */
for(i = 0; i < 5; i++)
{
printf("new pthread!i=%d\n", i);
sleep(1);
}
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);/* 设置为可被取消 */
printf("I'm printf() Cancellation points!\n");
return (void *)0;
}在终端执行以下命令编译程序:
gcc test.c -Wall -l pthread # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
This is main thread,ret=0,tid=139932233295424
This is a thread test!
new pthread!i=0
main thread!i=0
new pthread!i=1
new pthread!i=2
main thread!i=1
----------------
cancel thread!
----------------
new pthread!i=3
new pthread!i=4
I'm printf() Cancellation points!
thread test end, code=-1这说明取消点函数是执行完毕后退出的。
3.4 添加取消点
假设线程执行的是一个不含取消点的循环(譬如 for 循环、 while 循环),那么这时线程永远也不会响应取消请求,也就意味着除了线程自己主动退出,其它线程将无法通过向它发送取消请求而终止它,,但实际需求是,该线程必须可以被其它线程通过发送取消请求的方式终止,那这个时候怎么办?此时可以使用 pthread_testcancel() ,该函数目的很简单,就是产生一个取消点,线程如果已有处于挂起状态的取消请求,那么只要调用该函数,线程就会随之终止。
3.4.1 pthread_testcancel()
3.4.1.1 函数说明
在 linux 下可以使用 man pthread_testcancel 命令查看该函数的帮助手册。
/* Compile and link with -pthread. */
#include <pthread.h>
void pthread_testcancel(void);【函数说明】设置一个取消点。
【函数参数】 none
【返回值】 none
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#include <pthread.h>
/* 至少应该有的语句 */
pthread_testcancel()【注意事项】 none
3.4.1.2 使用实例
#include <stdio.h>
#include <pthread.h>/* pthread_create pthread_exit pthread_cancel pthread_testcancel pthread_self*/
#include <unistd.h> /* sleep */
#include <string.h>
#define macro 0
void *threadCancelType(void *arg);
int main(int argc, char *argv[])
{
int ret;
int i = 0;
pthread_t tid;
void *retv;
/* 1. 创建一个线程 */
ret = pthread_create(&tid, NULL, (void *)threadCancelType, NULL);
printf("This is main thread,ret=%d,tid=%lu\n", ret, tid);
for(i = 0; i < 2; i++)
{
sleep(1);
printf("main thread!i=%d\n", i);
}
/* 2. 申请取消线程 */
printf("----------------\ncancel thread!\n----------------\n");
pthread_cancel(tid);
/* 3. 回收线程,若取消成功,这里将会返回错误,然后打印信息 */
ret = pthread_join(tid, &retv);
if(ret != 0)
printf("pthread_join error: %s\n", strerror(ret));
printf("thread test end, code=%ld\n", (long)retv);
return 0;
}
void *threadCancelType(void *arg)
{
int i = 0;
printf("This is a thread test!\n");
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);/* 到达取消点取消 */
while(1)
{
/* 屏蔽 sleep printf两个函数的取消点功能 */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);/* 设置为不可被取消 */
sleep(1);
printf("new thread!i=%d\n", i);
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);/* 设置为可被取消 */
/* 设置取消点 */
if(i++ == 6 && macro == 1)
pthread_testcancel();
}
return (void *)0;
}程序中的宏 macro 用于配置新线程的取消类型。
- 若 macro 为 1 ,则新线程自己添加一个取消点。
在终端执行以下命令编译程序:
gcc test.c -Wall -l pthread # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
This is main thread,ret=0,tid=140083039794752
This is a thread test!
main thread!i=0
new thread!i=0
main thread!i=1
----------------
cancel thread!
----------------
new thread!i=1
new thread!i=2
new thread!i=3
new thread!i=4
new thread!i=5
new thread!i=6
thread test end, code=-1这个时候,当 i = 6 的时候设置取消点,线程被取消。
- 若 macro 为 0 ,则新线程不设置取消点。
在终端执行以下命令编译程序:
gcc test.c -Wall -l pthread # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
This is main thread,ret=0,tid=140136216446528
This is a thread test!
new thread!i=0
main thread!i=0
main thread!i=1
----------------
cancel thread!
----------------
new thread!i=1
new thread!i=2
new thread!i=3
new thread!i=4
new thread!i=5
new thread!i=6
new thread!i=7
new thread!i=8此种情况中,新线程是无法被取消的。
二、线程清理
前边学习了 atexit() 函数,使用 atexit() 函数注册进程终止处理函数,当进程调用 exit() 退出时就会执行进程终止处理函数;其实,当线程退出时也可以这样做,当线程终止退出时,去执行这样的处理函数,我们把这个函数称为线程清理函数( thread cleanup handler )。
一个线程可以注册多个清理函数,这些清理函数记录在栈中,每个线程都可以拥有一个清理函数栈,栈是一种先进后出的数据结构,也就是说它们的执行顺序与注册(添加)顺序相反,当执行完所有清理函数后,线程终止。
【注意事项】同一个清理函数可以被注册多次,满足执行条件的话注册多少次就会执行多少次。
1.函数说明
1.1 pthread_cleanup_push()
在 linux 下可以使用 man pthread_cleanup_push 命令查看该函数的帮助手册。
/* Compile and link with -pthread. */
#include <pthread.h>
void pthread_cleanup_push(void (*routine)(void *), void *arg);【函数说明】该函数向清理函数栈中添加一个清理函数。
【函数参数】
- routine :是一个函数指针变量指向一个 routine() 函数,其中 routine() 函数无返回值,且只有一个 void * 类型参数。
- arg : void * 类型,当调用清理函数 routine() 时,将 arg 作为 routine() 函数的参数。
【返回值】 none
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#include <pthread.h>
/* 至少应该有的语句 */
pthread_cleanup_push(cleanup, NULL);
pthread_cleanup_pop(0);【注意事项】必须和 pthread_cleanup_pop() 成对使用。
1.2 pthread_cleanup_pop()
在 linux 下可以使用 man pthread_cleanup_pop 命令查看该函数的帮助手册。
/* Compile and link with -pthread. */
#include <pthread.h>
void pthread_cleanup_pop(int execute);【函数说明】该函数将清理函数栈中最顶层(也就是最后添加的函数,最后入栈)的函数移除。
【函数参数】
- execute : int 类型,如果为 0 ,清理函数不会被调用,只是将清理函数栈中最顶层的函数移除;如果参数 execute 为非 0 ,则除了将清理函数栈中最顶层的函数移除之外,还会执行相应的清理函数。
【返回值】 none
【使用格式】一般情况下基本使用格式如下:
/* 需要包含的头文件 */
#include <pthread.h>
/* 至少应该有的语句 */
pthread_cleanup_push(cleanup, NULL);
pthread_cleanup_pop(0);【注意事项】必须和 pthread_cleanup_pop() 成对使用。
2. 函数使用
2.1 函数实现方式
上边介绍完了那两个函数,接下来先踩一个坑,就是这俩函数为啥必须成对出现?为啥有几个 pthread_cleanup_push 就要有几个 pthread_cleanup_pop() 呢?
我们来查一下这两个函数的实现( pthread.h ):
# define pthread_cleanup_push(routine, arg) \
do { \
struct __pthread_cleanup_frame __clframe \
__attribute__ ((__cleanup__ (__pthread_cleanup_routine))) \
= { .__cancel_routine = (routine), .__cancel_arg = (arg), \
.__do_it = 1 };
/* Remove a cleanup handler installed by the matching pthread_cleanup_push.
If EXECUTE is non-zero, the handler function is called. */
# define pthread_cleanup_pop(execute) \
__clframe.__do_it = (execute); \
} while (0)尽管上面我们将 pthread_cleanup_push() 和 pthread_cleanup_pop() 称之为函数,但它们是通过宏来实现,可展开为分别由 { 和 } 所包裹的语句序列,所以必须在与线程相同的作用域中以匹配对的形式使用,必须一一对应着来使用。
当我们只使用 pthread_cleanup_push() 函数时,程序可能会报以下错误:
error: expected declaration or statement at end of input2.2 函数执行条件
线程清理函数执行的条件:
(1)线程调用 pthread_exit() 退出时。
(2)线程响应取消请求时。
(3)用非 0 参数调用 pthread_cleanup_pop() 。
除了以上三种情况之外,其它方式终止线程将不会执行线程清理函数,例如在某线程函数start中执行 return 语句退出时就不会执行清理函数。
2.3 pthread_exit() 触发清理函数实例
#include <stdio.h>
#include <pthread.h>/* pthread_create pthread_exit pthread_cancel pthread_setcancelstate*/
#include <unistd.h> /* sleep */
#include <string.h>
void *threadCleanUp(void *arg);
void threadCleanUp1(void *arg);
void threadCleanUp2(void *arg);
int main(int argc, char *argv[])
{
int ret;
pthread_t tid;
void *retv;
/* 1. 创建一个线程 */
ret = pthread_create(&tid, NULL, threadCleanUp, NULL);
printf("This is main thread,ret=%d,tid=%lu\n", ret, tid);
sleep(1);
/* 2. 回收线程,若取消成功,这里将会返回错误,然后打印信息 */
ret = pthread_join(tid, &retv);
if(ret != 0)
printf("pthread_join error: %s\n", strerror(ret));
printf("thread test end, code=%ld\n", (long)retv);
return 0;
}
void *threadCleanUp(void *arg)
{
int i = 0;
pthread_cleanup_push(threadCleanUp1,"threadCleanUp1 runing!");
pthread_cleanup_push(threadCleanUp2,"threadCleanUp2 runing!");
printf("This is a new thread!\n");
for(i = 0; i < 5; i++)
{
printf("thread runing[%d]!\n", i);
sleep(1);
}printf("The new thread is ready to exit!\n");
pthread_exit("thread return");
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return (void *)0;
}
void threadCleanUp1(void *arg)
{
printf("threadCleanUp1,arg=%s\n", (char*)arg);
}
void threadCleanUp2(void *arg)
{
printf("threadCleanUp2,arg=%s\n", (char*)arg);
}在终端执行以下命令编译程序:
gcc test.c -Wall -l pthread # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
This is main thread,ret=0,tid=140032863184448
This is a new thread!
thread runing[0]!
thread runing[1]!
thread runing[2]!
thread runing[3]!
thread runing[4]!
The new thread is ready to exit!
threadCleanUp2,arg=threadCleanUp2 runing!
threadCleanUp1,arg=threadCleanUp1 runing!
thread test end, code=938301435005052.4 pthread_cancel() 触发清理函数实例
2.4.1 实例1
#include <stdio.h>
#include <pthread.h>/* pthread_create pthread_exit pthread_cancel pthread_setcancelstate*/
#include <unistd.h> /* sleep */
#include <string.h>
void *threadCleanUp(void *arg);
void threadCleanUp1(void *arg);
void threadCleanUp2(void *arg);
int main(int argc, char *argv[])
{
int ret;
int i = 0;
pthread_t tid;
void *retv;
/* 1. 创建一个线程 */
ret = pthread_create(&tid, NULL, threadCleanUp, NULL);
printf("This is main thread,ret=%d,tid=%lu\n", ret, tid);
for(i = 0; i < 2; i++)
{
printf("main thread!i=%d\n", i);
sleep(1);
}
/* 2. 申请取消线程 */
printf("----------------\ncancel thread!\n----------------\n");
pthread_cancel(tid);
/* 3. 回收线程,若取消成功,这里将会返回错误,然后打印信息 */
ret = pthread_join(tid, &retv);
if(ret != 0)
printf("pthread_join error: %s\n", strerror(ret));
printf("thread test end, code=%ld\n", (long)retv);
return 0;
}
void *threadCleanUp(void *arg)
{
int i = 0;
pthread_cleanup_push(threadCleanUp1,"threadCleanUp1 runing!");
pthread_cleanup_push(threadCleanUp2,"threadCleanUp2 runing!");
printf("This is a thread test!\n");
for(i = 0; i < 5; i++)
{
printf("thread runing[%d]!\n", i);
sleep(1);
}
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return (void *)0;
}
void threadCleanUp1(void *arg)
{
printf("threadCleanUp1,arg=%s\n",(char*)arg);
}
void threadCleanUp2(void *arg)
{
printf("threadCleanUp2,arg=%s\n",(char*)arg);
}在终端执行以下命令编译程序:
gcc test.c -Wall -l pthread # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
This is main thread,ret=0,tid=139866019890752
main thread!i=0
This is a thread test!
thread runing[0]!
main thread!i=1
thread runing[1]!
----------------
cancel thread!
----------------
thread runing[2]!
threadCleanUp2,arg=threadCleanUp2 runing!
threadCleanUp1,arg=threadCleanUp1 runing!
thread test end, code=-12.4.2 实例2
用非 0 参数调用 pthread_cleanup_pop() 触发清理函数实例:
#include <stdio.h>
#include <pthread.h>/* pthread_create pthread_exit pthread_cancel pthread_setcancelstate*/
#include <unistd.h> /* sleep */
#include <string.h>
void *threadCleanUp(void *arg);
void threadCleanUp1(void *arg);
void threadCleanUp2(void *arg);
int main(int argc, char *argv[])
{
int ret;
pthread_t tid;
void *retv;
/* 1. 创建一个线程 */
ret = pthread_create(&tid, NULL, threadCleanUp, NULL);
printf("This is main thread,ret=%d,tid=%lu\n", ret, tid);
sleep(2);/* 这里停留一下,创建的线程才有时间运行 */
/* 2. 回收线程,若取消成功,这里将会返回错误,然后打印信息 */
ret = pthread_join(tid, &retv);
if(ret != 0)
printf("pthread_join error: %s\n", strerror(ret));
printf("thread test end, code=%ld\n", (long)retv);
return 0;
}
void *threadCleanUp(void *arg)
{
int i = 0;
pthread_cleanup_push(threadCleanUp1,"threadCleanUp1 runing!");
pthread_cleanup_push(threadCleanUp2,"threadCleanUp2 runing!");
printf("This is a thread test!\n");
for(i = 0; i < 5; i++)
{
printf("thread runing[%d]!\n", i);
sleep(1);
}
pthread_cleanup_pop(0);
pthread_cleanup_pop(1);/* 只执行这个回调函数 */
return (void *)0;
}
void threadCleanUp1(void *arg)
{
printf("threadCleanUp1,arg=%s\n",(char*)arg);
}
void threadCleanUp2(void *arg)
{
printf("threadCleanUp2,arg=%s\n",(char*)arg);
}在终端执行以下命令编译程序:
gcc test.c -Wall -l pthread # 生成可执行文件 a.out
./a.out # 执行可执行程序然后,终端会有以下信息显示:
This is main thread,ret=0,tid=140153667556928
This is a thread test!
thread runing[0]!
thread runing[1]!
thread runing[2]!
thread runing[3]!
thread runing[4]!
threadCleanUp1,arg=threadCleanUp1 runing!
thread test end, code=0