【线程退出】linux线程退出的几个函数
一、线程退出
1、pthread_kill
(1)引用
1 2 | #include <pthread.h> #include<signal.h> |
(2)函数原型
1 | int pthread_kill(pthread_t thread , int sig); |
(3)参数
- thread:线程ID
- sig:sig信号
(4)返回值
-
0:调用成功。
-
ESRCH:线程不存在。。
- EINVAL:信号不合法
(4)作用
向指定的线程传递sig信号。
(5)注意
(6)程序实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | /******************************* pthread_kill.c *******************************/ #include<stdio.h> #include <pthread.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <errno.h> #include <signal.h> void *func1() /*1秒钟之后退出*/ { sleep(1); printf ( "线程1(ID:0x%x)退出。\n" ,(unsigned int )pthread_self()); pthread_exit(( void *)0); } void *func2() /*5秒钟之后退出*/ { sleep(5); printf ( "线程2(ID:0x%x)退出。\n" ,(unsigned int )pthread_self()); pthread_exit(( void *)0); } void test_pthread(pthread_t tid) /*pthread_kill的返回值:成功(0) 线程不存在(ESRCH) 信号不合法(EINVAL)*/ { int pthread_kill_err; pthread_kill_err = pthread_kill(tid,0); if (pthread_kill_err == ESRCH) printf ( "ID为0x%x的线程不存在或者已经退出。\n" ,(unsigned int )tid); else if (pthread_kill_err == EINVAL) printf ( "发送信号非法。\n" ); else printf ( "ID为0x%x的线程目前仍然存活。\n" ,(unsigned int )tid); } int main() { int ret; pthread_t tid1,tid2; pthread_create(&tid1,NULL,func1,NULL); pthread_create(&tid2,NULL,func2,NULL); sleep(3); /*创建两个进程3秒钟之后,分别测试一下它们是否还活着*/ test_pthread(tid1); /*测试ID为tid1的线程是否存在*/ test_pthread(tid2); /*测试ID为tid2的线程是否存在*/ exit (0); } |
//kill给线程发信号
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | #include <pthread.h> #include <stdio.h> #include <sys/signal.h> #define NUMTHREADS 3 void sighand( int signo); void *threadfunc( void *parm) { pthread_t tid = pthread_self(); int rc; printf ( "Thread %u entered/n" , tid); rc = sleep(30); /* 若有信号中断则返回剩余秒数 */ printf ( "Thread %u did not get expected results! rc=%d/n" , tid, rc); return NULL; } void *threadmasked( void *parm) { pthread_t tid = pthread_self(); sigset_t mask; int rc; printf ( "Masked thread %lu entered/n" , tid); sigfillset(&mask); /* 将所有信号加入mask信号集 */ /* 向当前的信号掩码中添加mask信号集 */ rc = pthread_sigmask(SIG_BLOCK, &mask, NULL); if (rc != 0) { printf ( "%d, %s/n" , rc, strerror (rc)); return NULL; } rc = sleep(15); if (rc != 0) { printf ( "Masked thread %lu did not get expected results! rc=%d /n" , tid, rc); return NULL; } printf ( "Masked thread %lu completed masked work/n" , tid); return NULL; } int main( int argc, char **argv) { int rc; int i; struct sigaction actions; pthread_t threads[NUMTHREADS]; pthread_t maskedthreads[NUMTHREADS]; printf ( "Enter Testcase - %s/n" , argv[0]); printf ( "Set up the alarm handler for the process/n" ); memset (&actions, 0, sizeof (actions)); sigemptyset(&actions.sa_mask); /* 将参数set信号集初始化并清空 */ actions.sa_flags = 0; actions.sa_handler = sighand; /* 设置SIGALRM的处理函数 */ rc = sigaction(SIGALRM,&actions,NULL); printf ( "Create masked and unmasked threads/n" ); for (i=0; i<NUMTHREADS; ++i) { rc = pthread_create(&threads[i], NULL, threadfunc, NULL); if (rc != 0) { printf ( "%d, %s/n" , rc, strerror (rc)); return -1; } rc = pthread_create(&maskedthreads[i], NULL, threadmasked, NULL); if (rc != 0) { printf ( "%d, %s/n" , rc, strerror (rc)); return -1; } } sleep(3); printf ( "Send a signal to masked and unmasked threads/n" ); /* 向线程发送SIGALRM信号 */ for (i=0; i<NUMTHREADS; ++i) { rc = pthread_kill(threads[i], SIGALRM); rc = pthread_kill(maskedthreads[i], SIGALRM); } printf ( "Wait for masked and unmasked threads to complete/n" ); for (i=0; i<NUMTHREADS; ++i) { rc = pthread_join(threads[i], NULL); rc = pthread_join(maskedthreads[i], NULL); } printf ( "Main completed/n" ); return 0; } void sighand( int signo) { pthread_t tid = pthread_self(); printf ( "Thread %lu in signal handler/n" , tid); return ; } |
2、pthread_exit
1 | void pthread_exit( void * retval); |
(2)参数
pthread_exit函数唯一的参数value_ptr是函数的返回代码,只要pthread_join中的第二个参数value_ptr不是NULL,这个值将被传递给value_ptr。
(3)作用
1 | 线程通过调用pthread_exit函数终止执行,就如同进程在结束时调用 exit 函数一样。这个函数的作用是,终止调用它的线程并返回一个指向某个对象的指针。 |
(4)用法
- 在线程中调用pthread_exit退出线程,在主线程中嗲用pthread_join函数阻塞等待结束并释放资源,pthread_exit的参数会传给pthread_join.
- 设置线程为分离属性,线程中调用pthread_exit退出线程后系统自动释放资源。
- 在主线程中,如果从main函数返回或是调用了exit函数退出主线程,则整个进程将终止,此时进程中有线程也将终止,因此在主线程中不能过早地从main函数返回;
- 如果主线程调用pthread_exit函数,则仅仅是主线程消亡,进程不会结束,进程内的其他线程也不会终止,直到所有线程结束,进程才会结束;
3、pthread_join
函数pthread_join用来等待一个线程的结束,线程间同步的操作。
pthread_join()函数,以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable(合并)的。
(1)函数原型
1 | int pthread_join( pthread_t thread , void * * value_ptr ); |
(2)参数
1 2 | thread :线程ID retval:用户定义的指针,用来存储被等待线程的返回值。 |
(3)返回值
1 | 0代表成功。 失败,返回的则是错误号。 |
(4)解释
- 代码中如果没有pthread_join主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
- 通过pthread_join()函数来使主线程阻塞等待其他线程退出,这样主线程可以清理其他线程的环境。但是还有一些线程,更喜欢自己来清理退出的状态,他们也不愿意主线程调用pthread_join来等待他们。我们将这一类线程的属性称为detached(分离的)。如果我们在调用 pthread_create()函数的时候将属性设置为NULL,则表明我们希望所创建的线程采用默认的属性,也就是jionable(此时不是detached)。
(1)作用
- 异步终结:当其他线程调用pthread_cancel的时候,线程就立刻被结束。
- 同步终结:同步终结则不会立刻终结,它会继续运行,直到到达下一个结束点(cancellation point)。当一个线程被按照默认的创建方式创建,那么它的属性是同步终结。
若是在整个程序退出时,要终止各个线程,应该在成功发送 CANCEL 指令后,使用 pthread_join 函数,等待指定的线程已经完全退出以后,再继续执行;否则,很容易产生 “段错误”。
(2)函数原型
1 2 | #include<pthread.h> int pthread_cancel(pthread_t thread ) |
(3)返回值
1 | 成功则返回0,否则为非0值。发送成功并不意味着 thread 会终止。 |
(4)用法
1 | int pthread_setcancelstate( int state, int *oldstate) |
- PTHREAD_CANCEL_ENABLE(缺省):收到信号后设为CANCLED状态
- PTHREAD_CANCEL_DISABLE:忽略CANCEL信号继续运行;
old_state如果不为 NULL则存入原来的Cancel状态以便恢复。
1 | int pthread_setcanceltype( int type, int *oldtype) |
- PTHREAD_CANCEL_DEFERRED:表示收到信号后继续运行至下一个取消点再退出
- PTHREAD_CANCEL_ASYNCHRONOUS:立即执行取消动作(退出)
oldtype如果不为NULL则存入原来的取消动作类型值。
1 | void pthread_testcancel( void ) |
(5)程序实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> void * func( void *arg) { pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); //允许退出线程 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); //设置立即取消 while (1); return NULL; } int main( int argc, char *argv[]) { pthread_t thrd; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED); if (pthread_create(&thrd,&attr,func,NULL)) { perror ( "pthread_create error " ); exit (EXIT_FAILURE); } if (!pthread_cancel(thrd)) { printf ( "pthread_cancel OK\n" ); } sleep(10); return 0;} |
5、exit()
6、return
- pthread_join一般是主线程来调用,用来等待子线程退出,因为是等待,所以是阻塞的,一般主线程会依次join所有它创建的子线程。
- pthread_exit一般是子线程调用,用来结束当前线程。子线程可以通过pthread_exit传递一个返回值,而主线程通过pthread_join获得该返回值,从而判断该子线程的退出是正常还是异常。
二、资源清理
一旦又处于挂起状态的取消请求(即加锁之后,解锁之前),线程在执行到取消点时如果只是草草收场,这会将共享变量以及pthreads对象(例如互斥量)置于一种不一致状态,可能导致进程中其他线程产生错误结果、死锁,甚至造成程序崩溃。为避免这一问题:
使用清理函数pthread_cleanup_push()和pthread_cleanup_pop()来处理,这两个函数必需成对出现,不然会编译错误。
不论是可预见的线程种植还是异常终止,都会存在资源释放的问题,在不考虑因运行出错而退出的前提下,如何保证种植时能顺利的释放掉自己所占用的资源,包括单独申请的对内存,特别是锁资源,就是一个必需考虑的问题。
最近常出现的情形时资源独占锁的使用:
线程为了访问临界共享资源而为其加上锁,但在访问过程呗外界取消,或者发生了中断,则该临界资源将永远处于锁定状态得不到释放。外界取消操作是不可见的,因此的确需要一个机制来简化用于资源释放的编程。
在POSIX线程API中提供了一个pthread_clean_push()/pthread_cleanup_pop()函数对,用于自动释放资源----从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作都将执行pthread_cleanup_push()所指定的清理函数。
API定义如下:
1 2 | void pthread_cleanup_push( void (*routine) ( void *), void *arg) void pthread_cleanup_pop( int execute) |
pthread_cleanup_push()/pthread_cleanup_pop()采用先入后出的栈结构管理,
void routine(void *arg)函数在调用pthread_cleanup_push()时压入清理函数栈,多次对pthread_cleanup_push()的调用将在清理函数栈中形成一个函数链,在执行该函数链时按照压栈的相反顺序弹出。execute参数表示执行到pthread_cleanup_pop()时是否在弹出清理函数的同时执行该函数,为0表示不执行,非0为执行;这个参数并不影响异常终止时清理函数的执行。
有三种情况线程清理函数会被调用:
- 线程还未执行 pthread_cleanup_pop 前,被 pthread_cancel 取消
- 线程还未执行 pthread_cleanup_pop 前,主动执行 pthread_exit 终止
- 线程执行 pthread_cleanup_pop,且 pthread_cleanup_pop 的参数不为 0.
注意:如果线程还未执行 pthread_cleanup_pop 前通过 return 返回,是不会执行清理函数的。
void routine()函数可参照如下定义:
1 2 3 4 5 | void *cleanup( void * p) { free (p); printf ( "清理函数\n" ); } |
线程主动清理过程的严谨写法:
1 2 3 4 5 6 7 8 9 | void thread_fun( void *p) { p= malloc (20); pthread_cleanup_push(cleanup,p); printf ( "子线程\n" ); sleep(1); //系统调用,用来响应pthread_cancel函数 printf ( "是否杀死了线程\n" ); //如果线程在上一句被杀死,这一句不会被打印 pthread_exit(NULL); //不管线程是否被杀死,这一句都会检测清理函数,并执行 pthread_clean_pop(1); } |
注意:在子线程中如果申请了单独的堆空间,不应用free直接清理;因为假如在线程中直接free,如果,在free之后线程被取消,清理函数被执行,则会出现重复free的情况。
情况如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | void thread_fun( void *p) { p= malloc (20); pthread_cleanup_push(cleanup,p); printf ( "子线程\n" ); sleep(1); //系统调用,用来响应pthread_cancel函数 printf ( "是否杀死了线程\n" ); //如果线程在上一句被杀死,这一句不会被打印 free (p); //不管线程是否被杀死,这一句都会检测清理函数,并执行<br> //加入函数在此处被cancel ,可能会出现重复free sleep(1); //在此处系统调用,响应pthread_cancel(),会执行清理函数. return NULL; pthread_clean_pop(1); //由于上一句return,所以这一句不执行,即清理函数不会执行 } |
pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式实现的,这是pthread.h中的宏定义:
1 2 3 4 5 6 7 | #define pthread_cleanup_push(routine,arg) { struct _pthread_cleanup_buffer _buffer; _pthread_cleanup_push (&_buffer, (routine), (arg)); #define pthread_cleanup_pop(execute) _pthread_cleanup_pop (&_buffer, (execute)); } |
可见,pthread_cleanup_push()带有一个"{",而pthread_cleanup_pop()带有一个"}",因此这两个函数必须成对出现,且必须位于程序的同一级别的代码段中才能通过编译。
在下面的例子里,当线程在"do some work"中终止时,将主动调用pthread_mutex_unlock(mut),以完成解锁动作。
以下是使用方法:
1 2 3 4 5 | pthread_cleanup_push(pthread_mutex_unlock, ( void *) &mut); pthread_mutex_lock(&mut); ....... pthread_mutex_unlock(&mut); pthread_cleanup_pop(0); |
必须要注意的是,如果线程处于PTHREAD_CANCEL_ASYNCHRONOUS状态,上述代码段就有可能出错,因为CANCEL事件有可能在pthread_cleanup_push()和pthread_mutex_lock()之间发生,或者在pthread_mutex_unlock()和pthread_cleanup_pop()之间发生,
从而导致清理函数unlock一个并没有加锁的mutex变量,造成错误。因此,在使用清理函数的时候,都应该暂时设置成PTHREAD_CANCEL_DEFERRED模式。为此,POSIX的Linux实现中还提供了一对不保证可移植的pthread_cleanup_push_defer_np()/pthread_cleanup_pop_defer_np()
扩展函数,
功能与以下代码段相当:
1 2 3 4 5 6 7 8 | { int oldtype; pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype); pthread_cleanup_push(routine, arg); ...... pthread_cleanup_pop(execute); pthread_setcanceltype(oldtype, NULL); } |
补充:
在线程宿主函数中主动调用return,如果return语句包含在pthread_cleanup_push()/pthread_cleanup_pop()对中,则不会引起清理函数的执行,反而会导致segment fault。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
2019-10-14 WIFI-Direct(Wifi直连)、AirPlay、DLAN、Miracast功能介绍
2019-10-14 各手机PC品牌投屏功能连接方法