咨询pthread线程未释放锁即被取消是否会引发死锁及解决措施
我是C语言多线程开发的新手,编写了一个简单程序:主线程启动若干工作线程,这些线程通过条件变量等待start_flag被置位;随后主线程启动另一个starter线程,该线程设置flag并通过pthread_cond_broadcast()通知所有线程。
工作线程的start_routine代码如下:
void* workerThread() { pthread_mutex_lock(&start_flag_mutex); while(!start_flag) { pthread_cond_wait(&start_flag_condition, &start_flag_mutex); } pthread_mutex_unlock(&start_flag_mutex); //Do some work here... }
starter线程的start_routine代码如下:
void* starterThread() { pthread_mutex_lock(&start_flag_mutex); start_flag = true; pthread_cond_broadcast(&start_flag_condition); pthread_mutex_unlock(&start_flag_mutex); }
主线程启动所有线程后,按先工作线程、后starter线程的顺序调用pthread_join。所有pthread原语均已正确初始化和销毁。但约每10次运行中就有1次,程序会因工作线程卡在条件等待而停滞。
我的问题是:持有共享资源(此处为flag)锁的工作线程是否可能在释放锁前被取消,从而导致其他线程永久等待?若是,该如何预防?
补充说明:主线程会先取消工作线程,待其完成工作后再调用pthread_join,代码如下:
for (size_t i = 0; i < NUM_WORKERS; ++i) { pthread_cancel(worker_tids[i]); pthread_join(worker_tids[i], NULL); }
你的猜测完全正确——持有锁的工作线程被取消时,会导致锁被永久持有,进而让其他线程(包括starter线程)无法获取锁修改flag或发送通知,最终造成工作线程卡在条件等待上。
具体来说,当工作线程在pthread_mutex_lock(&start_flag_mutex)之后、pthread_mutex_unlock(&start_flag_mutex)之前被主线程pthread_cancel,且未设置线程取消清理函数时,线程会直接终止,start_flag_mutex会一直处于锁定状态:
- starter线程尝试加锁时会被阻塞,永远无法设置
start_flag和发送广播; - 其他工作线程要么卡在等待锁,要么卡在条件变量等待,最终整个程序停滞。
针对这个问题,有以下几种可行的预防方案:
1. 为线程注册取消清理函数
在工作线程加锁后,立即注册清理函数,确保线程被取消时能自动释放锁。修改工作线程代码如下:
void cleanup_mutex(void *arg) { pthread_mutex_unlock((pthread_mutex_t *)arg); } void* workerThread() { pthread_mutex_lock(&start_flag_mutex); // 注册清理函数,线程取消时自动调用以释放锁 pthread_cleanup_push(cleanup_mutex, &start_flag_mutex); while(!start_flag) { pthread_cond_wait(&start_flag_condition, &start_flag_mutex); } // 正常执行到此处时,取消清理函数(参数0表示不触发清理逻辑) pthread_cleanup_pop(0); pthread_mutex_unlock(&start_flag_mutex); //Do some work here... }
注意:pthread_cleanup_push和pthread_cleanup_pop必须成对出现,即使代码逻辑上不会走到pop,也不能省略。
2. 调整线程处理的顺序
主线程当前先取消工作线程再执行join,但如果starter线程还没完成设置flag和广播,工作线程可能还持有锁。正确的顺序应该是先等待starter线程完成,再处理工作线程:
// 先等待starter线程执行完毕,确保flag已设置且广播已发送 pthread_join(starter_tid, NULL); // 再等待工作线程完成任务 for (size_t i = 0; i < NUM_WORKERS; ++i) { pthread_join(worker_tids[i], NULL); }
如果确实需要提前取消工作线程,也要确保在取消前,所有线程都已释放锁,或者通过方案1的清理函数保证锁被释放。
3. 按需禁用线程取消
如果业务场景不需要动态取消线程,可以在工作线程启动时直接禁用取消:
void* workerThread() { // 禁用线程取消,避免中途被终止 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); pthread_mutex_lock(&start_flag_mutex); while(!start_flag) { pthread_cond_wait(&start_flag_condition, &start_flag_mutex); } pthread_mutex_unlock(&start_flag_mutex); // 若后续工作需要允许取消,再重新启用 pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); //Do some work here... }
这种方式可以从根源避免因取消导致的锁泄漏问题,但要确认业务逻辑允许线程不能被中途取消。
4. 利用条件变量的取消安全特性
pthread_cond_wait本身是取消点,当线程在条件变量等待时被取消,会自动释放锁(这是条件变量API的内置特性)。但问题出在线程已经退出条件等待但还没释放锁时被取消,或者还没进入条件等待就被取消的场景,所以结合方案1的清理函数,就能覆盖所有持有锁的情况。
内容的提问来源于stack exchange,提问作者linuxuser

