条件变量和 mutex_unlock

Condition variables and mutex_unlock

代码:

void *inc_func(void *arg)
{
    pthread_mutex_lock(&mutex);
    pthread_cond_signal(&count_threshold_cv);
    sleep(1);
    pthread_mutex_unlock(&mutex);
}
void *watch(void *arg)
{
    pthread_mutex_lock(&mutex);
    pthread_cond_wait(&count_threshold_cv,&mutex);
    sleep(1);
    pthread_mutex_unlock(&mutex);
}
int main()
{
    pthread_t id[2];
    pthread_create(&id[0],NULL,watch,NULL);
    pthread_create(&id[1],NULL,inc_func,NULL);
    int i;
    for(i=0;i<2;i++)
        pthread_join(id[i],NULL);
}

现在我们有一个 mutex_unlock 函数要在每个线程中执行。和一个锁定的互斥量。为什么这不会导致 undefined behaviour。由于两个线程都试图解锁同一个互斥锁,这导致一个线程试图解锁一个已经解锁的互斥锁。

编辑:pthread_cond_wait 释放 mutex 供第二个线程使用。现在考虑第二个线程执行 pthread_cond_signal,这导致第一个线程重新获取 mutex。现在我们有两个具有相同 mutex 锁的线程,因为由于 'sleep' 函数,两个线程都没有执行 mutex_unlock。我的理解有误吗?

  • pthread_mutex_lock() 如果要锁定的互斥体已经被锁定,则阻塞。它 returns 如果互斥量被解锁。

  • pthread_cond_wait() 在开始等待时解锁互斥锁,在返回之前锁定。如果有问题的互斥量仍处于锁定状态,则返回可能会延迟。然后返回将延迟,直到互斥量被解锁。

将以上内容放在一起并将其应用到您展示的代码中,您会发现每个线程函数都很好地以锁定开始,然后是解锁(依此类推),所以一切都很好。

参考示例代码:pthread_cond_wait() returns when inc_func() had call pthread_mutex_unlock().


要成功处理示例代码描述的场景,您需要考虑两种特殊情况

  1. 信号先到的情况
  2. 所谓的 "spurious wake-ups" 的情况,即 pthread_cond_wait() 在没有收到信号的情况下返回。

要处理这两种情况,每个条件都应该有一个监视变量。

pthread_mutex_t mutex = ...
pthread_cond_t count_threshold_cv = ...

int signalled = 0;

void *inc_func(void *arg)   
{
  pthread_mutex_lock(&mutex);

  pthread_cond_signal(&count_threshold_cv);
  signalled = 1;

  pthread_mutex_unlock(&mutex);  
}

void *watch(void *arg)
{
  pthread_mutex_lock(&mutex);

  while (0 == signalled)
  {
    pthread_cond_wait(&count_threshold_cv,&mutex);
  }

  pthread_mutex_unlock(&mutex);
}

int main(void)
{
  pthread_t id[2];
  pthread_create(&id[0],NULL,watch,NULL);
  pthread_create(&id[1],NULL,inc_func,NULL);
  int i;
  for(i=0;i<2;i++)
    pthread_join(id[i],NULL);
}  

如果订单确实是

  • watch 首先运行并锁定(而 inc_func 等待)
  • watch 有互斥等待使用 pthread_cond_wait 解锁互斥并根据 documentation

These functions atomically release mutex and cause the calling thread to block on the condition variable cond;

这允许以下

  • inc_func获取互斥量然后发信号(此时互斥量尚未释放)
  • inc_func 释放互斥量
  • 因为互斥量已释放且互斥量已解锁 watch 的执行恢复并已根据 documentation:
  • 锁定互斥量

Upon successful return, the mutex has been locked and is owned by the calling thread.

  • 接下来是互斥量的合法释放。

你没有考虑的场景是如果inc_func的代码先执行而不切换到watch

会怎样
  • inc_func 锁定互斥体
  • inc_func 发出信号但没有人发出信号,这没关系。
  • inc_func 解锁互斥
  • watch 锁定互斥体
  • 然后等待条件变量,但没有人向它发出信号,因此它将永远等待。