You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

咨询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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 06:15:27