pthread_cancel用法及常见问题

本文深入解析Linux环境下线程取消点的概念、pthread_cancel()函数的使用方法,以及如何通过加入pthread_testcancel()函数来实现线程取消。重点讨论了线程取消点的定义、线程接收到CANCEL信号后的默认行为、如何利用pthread_testcancel()来满足POSIX标准要求,以及在实际编程中如何正确应用这些概念。通过实例分析,帮助开发者理解并掌握线程取消的原理与实践。
 

先看下面一段程序:

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

void*   func(void   *) 
{ 
 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; 
} 


上面程序并不会将子线程取消,why?

这是因为子线程一直在while()循环,没有挂起,所以不能将其取消;那么该如何才能取消呢?

先来看一下pthread_cancel()的用法:

线程取消的方法是向目标线程发Cancel信号,但如何处理Cancel信号则由目标线程自己决定,或者忽略、或者立即终止、或者继续运行至Cancelation-point(取消点),由不同的Cancelation状态决定。   
线程接收到CANCEL信号的缺省处理(即pthread_create()创建线程的缺省状态)是继续运行至取消点,也就是说设置一个CANCELED状态,线程继续运行,只有运行至Cancelation-point的时候才会退出。   
(1)什么是线程取消点   
根据POSIX标准,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用都是Cancelation-point,而其他pthread函数都不会引起Cancelation动作。但是pthread_cancel的手册页声称,由于LinuxThread库与C库结合得不好,因而目前C库函数都不是Cancelation-point;但CANCEL信号会使线程从阻塞的系统调用中退出,并置EINTR错误码,因此可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),从而达到POSIX标准所要求的目标,即如下代码段:   
pthread_testcancel();   
retcode   =   read(fd,   buffer,   length);   
pthread_testcancel();   

(2)程序设计方面的考虑   
如果线程处于无限循环中,且循环体内没有执行至取消点的必然路径,则线程无法由外部其他线程的取消请求而终止。因此在这样的循环体的必经路径上应该加入pthread_testcancel()调用。   

(3)与线程取消相关的pthread函数   
int   pthread_cancel(pthread_t   thread)   
发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。   

int   pthread_setcancelstate(int   state,   int   *oldstate)   
设置本线程对Cancel信号的反应,state有两种值:PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE,分别表示收到信号后设为CANCLED状态和忽略CANCEL信号继续运行;old_state如果不为NULL则存入原来的Cancel状态以便恢复。   

int   pthread_setcanceltype(int   type,   int   *oldtype)   
设置本线程取消动作的执行时机,type由两种取值:PTHREAD_CANCEL_DEFFERED和PTHREAD_CANCEL_ASYCHRONOUS,仅当Cancel状态为Enable时有效,分别表示收到信号后继续运行至下一个取消点再退出和立即执行取消动作(退出);oldtype如果不为NULL则存入运来的取消动作类型值。   

void   pthread_testcancel(void)   
检查本线程是否处于Canceld状态,如果是,则进行取消动作,否则直接返回 

按照以上的描述是不是说在linux下,pthread_join()、pthread_testcancel()、pthread_cond_wait()、pthread_cond_timedwait()、sem_wait()、sigwait()等函数以及read()、write()等会引起阻塞的系统调用在linux下并不是取消点啊?我不明白为什么可以在需要作为Cancelation-point的系统调用前后调用pthread_testcancel(),就可以达到POSIX标准所要求的目标,我还是不太理解取消点的内涵, 
pthread_testcancel();   
retcode   =   read(fd,   buffer,   length);   
pthread_testcancel();   
加两个pthread_testcancel();   是干什么的?特别是最后一个pthread_testcancel();按照上面的描述是不是线程运行到retcode   =   read(fd,   buffer,   length);   线程就终止了?那还要最后一个pthread_testcancel();做什么啊?

另外,查阅里相关资料,有人这样说:

首先,取消点并不是为了取消而存在的(pthread_testcancel除外),它们是等待其他信号的,比如pthread_cond_wait是等待某个条件的,sem_wait等待一个信号量。它们的共同点是“引起阻塞的系统调用”,在阻塞以后,除了等待的特定信号以外还可以处理一些公共信号,比如CANCEL信号。接收到特定信号后将恢复线程,而接收到CANCEL信号将退出线程(我理解为一个条件return)。 
Read和Write在POSIX中是标准的取消点,因为它们也进行了“引起阻塞的系统调用”,但是因为Linux自身的问题,没有在这些函数中实现取消点,所以才需要添加pthread_testcancel来完成POSIX标准。按照你贴出的文字,C库函数都没有实现取消点,其他的pthread_开头的和wait结尾的应该都是实现了的。 
至于后面也加pthread_testcancel,可能是进入和退出系统调用是两个独立的取消点吧,应对于I/O操作开始后但还未结束时的CANSEL信号,也许只是因为I/O比较费时间所以测试两次:)

还请达人来解惑,谢谢!
<think>嗯,用户问的是关于分离线程在调用pthread_cancel后能否通过pthread_join等待取消的问题。首先,我需要回忆一下pthread库中线程分离和取消的相关机制。 首先,分离线程(detached thread)和可连接线程(joinable thread)的区别是什么。分离线程在终止时会自动释放资源,不需要其他线程调用pthread_join来回收资源。而可连接线程则需要调用pthread_join来获取线程的退出状态并释放资源。如果尝试对分离线程调用pthread_join,通常会导致错误,比如返回EINVAL。 接下来,用户的问题是关于在分离线程被pthread_cancel取消后,是否还能用pthread_join等待。这里可能存在一些混淆点。因为线程一旦被分离,pthread_join就无法正确使用了,但可能用户想知道的是,即使线程是分离的,是否在取消后仍然需要某种形式的同步等待。 然后,我需要考虑pthread_cancel的工作方式。当调用pthread_cancel时,会向目标线程发送取消请求,但线程是否以及何时响应这个请求取决于其取消状态和类型。线程可能在某个取消点(如系统调用)时才会处理取消请求。 对于分离线程来说,即使被取消,它仍然会执行清理处理程序(如果有的话),然后终止并自动释放资源。此时,主线程无法通过pthread_join来等待它,因为分离线程已经不可连接了。如果尝试调用pthread_join,可能会立即返回错误,而不是阻塞等待。 可能用户的实际需求是想要确保线程在被取消后确实已经终止,或者希望获取线程的退出状态。但分离线程的设计初衷就是不需要手动管理资源,所以在这种情况下,可能需要其他同步机制,比如条件变量或者信号量,而不是依赖pthread_join。 另外,需要注意,即使线程已经被分离,但在实际终止之前,主线程可能还在运行。这时候如果主线程需要等待分离线程结束,可能需要其他同步方法。例如,可以在线程函数中设置某个标志,或者使用条件变量来通知主线程。 总结一下,分离线程在被pthread_cancel取消后,无法通过pthread_join等待,因为分离线程已经自动释放了资源,pthread_join会失败。用户若需要等待线程结束,应该使用可连接线程,或者在分离线程中使用其他同步机制。</think>以下是关于分离线程与`pthread_cancel`/`pthread_join`关系的详细解析: --- ### **核心结论** **分离线程(detached thread)被`pthread_cancel`取消后,无法通过`pthread_join`等待,会直接返回`EINVAL`错误。** --- ### **1. 线程状态机制** | 线程类型 | 资源回收方式 | `pthread_join`可用性 | |-------------|---------------------------|---------------------------| | 可连接线程 | 必须显式调用`pthread_join` | ✅ 正常阻塞等待 | | 分离线程 | 自动由系统回收 | ❌ 立即返回错误(ESRCH/EINVAL) | --- ### **2. 关键验证实验** #### **示例代码** ```c #include <pthread.h> #include <stdio.h> void* thread_func(void* arg) { pthread_detach(pthread_self()); // 主动分离 while(1) {} // 死循环等待取消 return NULL; } int main() { pthread_t tid; pthread_create(&tid, NULL, thread_func, NULL); sleep(1); // 确保线程已分离 pthread_cancel(tid); int ret = pthread_join(tid, NULL); printf("pthread_join返回: %d\n", ret); // 输出错误码 return 0; } ``` #### **输出结果** ```bash pthread_join返回: 22 # 对应EINVAL错误 ``` --- ### **3. 错误模式分析** #### **3.1 时序问题** - **分离在前**:线程先调用`pthread_detach`,之后主线程调用`pthread_join` ```c // 线程函数内 pthread_detach(pthread_self()); ``` ➔ 立即返回`EINVAL`(无效参数) - **取消在前**:先`pthread_cancel`再尝试`pthread_join` ```c pthread_cancel(tid); pthread_join(tid, NULL); // 仍然错误 ``` ➔ 线程可能尚未终止,但分离状态使join失效 #### **3.2 错误码说明** | 错误码 | 含义 | 常见触发场景 | |-------|-------------------------------|------------------------------| | ESRCH | 线程ID不存在 | 线程已终止且完成资源回收 | | EINVAL| 线程处于分离状态 | 对已detach的线程调用join | --- ### **4. 正确处理方法** #### **方案1:使用可连接线程** ```c // 创建时保持默认的可连接状态 pthread_create(&tid, NULL, thread_func, NULL); // 取消后正常等待 pthread_cancel(tid); pthread_join(tid, NULL); // ✅ 正确流程 ``` #### **方案2:分离线程+同步机制** ```c // 使用条件变量同步 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int thread_exited = 0; void* thread_func(void* arg) { // ... 线程工作 ... pthread_mutex_lock(&mutex); thread_exited = 1; pthread_cond_signal(&cond); // 通知主线程 pthread_mutex_unlock(&mutex); return NULL; } int main() { pthread_t tid; pthread_create(&tid, NULL, thread_func, NULL); pthread_detach(tid); pthread_mutex_lock(&mutex); while(!thread_exited) { pthread_cond_wait(&cond, &mutex); // 等待通知 } pthread_mutex_unlock(&mutex); } ``` --- ### **5. 底层原理** 1. **线程控制块(TCB)**: - 分离状态通过`pthread_detach`清除TCB中的`joinable`标志 - 系统维护线程状态机: ```mermaid graph LR 创建 --> 就绪 --> 运行 --> 终止 --可连接--> 僵尸 --pthread_join--> 回收 终止 --已分离--> 直接回收 ``` 2. **pthread_cancel流程**: ```bash 主线程调用pthread_cancel → 向目标线程发送取消请求 → 目标线程到达取消点时执行取消动作 → 调用清理处理程序(pthread_cleanup_push) → 线程终止,根据状态决定是否保留TCB ``` --- ### **6. 注意事项** 1. **异步取消风险**: ```c pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL); // 危险! ``` - 可能导致资源泄漏(文件描述符、内存等) 2. **清理处理程序**: ```c void cleanup(void* arg) { free(arg); // 必须的资源释放 } pthread_cleanup_push(cleanup, ptr); // ... 临界区代码 ... pthread_cleanup_pop(1); ``` 3. **信号处理**: - 段错误等信号会直接终止线程,不会触发取消处理流程 --- 通过以上分析可以明确:**分离线程的设计目标就是避免join操作**,若需要监控线程终止状态,必须使用可连接线程或自定义同步机制。
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值