时间:2011-09-24 00:15:29

标签: c pthreads posix race-condition condition-variable

假设在信令线程修改影响谓词真值的状态并调用pthread_cond_signal而不保持与条件变量关联的互斥锁的情况下使用条件变量?这种类型的使用始终是否适用于可能错过信号的竞争条件?

对我来说,似乎总是有一个明显的种族:

  1. 服务员将谓词评估为false,但在它开始等待之前......
  2. 另一个线程以使谓词成立的方式改变状态。
  3. 其他线程调用pthread_cond_signal,因为还没有服务员,所以什么都不做。
  4. 服务员线程进入pthread_cond_wait,不知道谓词现在是真的,并且无限期地等待。
  5. 但是如果情况发生变化,这种相同的竞争条件是否总是存在,以便(A)在调用pthread_cond_signal时保持互斥锁,而不是在改变状态时,或者(B)使得更改状态时保持互斥锁,而不是在调用pthread_cond_signal时使用?

    我是否想要知道是否有任何有效用途的上述非最佳实践用法,即正确的条件变量实施是否需要考虑避免竞争条件的这种用法本身,或者它是否可以忽略它们,因为它们已经天生就是生涩。

3 个答案:

答案 0 :(得分:2)

状态必须在互斥锁中进行修改,除非出于虚假唤醒的可能性,否则会导致读者在写作的过程中读取状态。

您可以在状态更改后随时致电pthread_cond_signal。它不必在互斥锁内。 POSIX保证唤醒至少一名服务员以检查新状态。更重要的是:

  1. 调用pthread_cond_signal并不能保证读者首先获得互斥锁。另一位作家可能会在读者有机会检查新状态之前进入。条件变量并不能保证读者立即关注作者(毕竟,如果没有读者会怎样?)
  2. 释放锁之后调用它实际上更好,因为你不会冒险让刚刚醒来的读者立即重新入睡,试图获得作者仍然持有的锁。
  3. 编辑: @DietrichEpp在评论中提出了一个很好的观点。作者必须以这样的方式改变状态,即读者永远不能访问不一致的状态。它可以通过获取条件变量中使用的互斥锁来实现,如上所述,或者确保所有状态更改都是原子的。

答案 1 :(得分:2)

这里的基本竞赛如下:

THREAD A        THREAD B
Mutex lock
Check state
                Change state
                Signal
cvar wait
(never awakens)

如果我们对状态变化或信号或两者都采取锁定,那么我们就避免这种情况;当线程A处于其临界区并持有锁时,状态变化和信号都不可能发生。

如果我们考虑相反的情况,其中线程A交错到线程B,没有问题:

THREAD A        THREAD B
                Change state
Mutex lock
Check state
( no need to wait )
Mutex unlock
                Signal (nobody cares)

因此,线程B没有特别需要在整个操作中保存互斥锁;它只需要在状态变化和信号之间保持一些可能无限小间隔的互斥锁。当然,如果状态本身需要锁定以进行安全操作,那么锁也必须保持在状态变化之上。

最后,请注意,在大多数情况下,提前删除互斥锁不太可能是性能提升。要求保持互斥锁可以减少对条件变量中内部锁的争用,并且在现代pthreads实现中,系统可以“等待”等待线程从等待cvar到等待互斥锁而不将其唤醒(从而避免它)醒来只是立即阻止互斥锁。) 正如评论中所指出的,在某些情况下,通过减少所需的系统调用次数,删除互斥锁可能可以提高性能。然后,它还可能导致条件变量的内部互斥锁上的额外争用。很难说。无论如何,这可能不值得担心。

请注意,applicable standards要求pthread_cond_signal可以安全地调用,而无需保留互斥锁:

  

线程可以调用pthread_cond_signal()或pthread_cond_broadcast()函数,无论它当前是否拥有调用pthread_cond_wait()或pthread_cond_timedwait()的线程在等待期间与条件变量相关联的互斥锁[...]

这通常意味着条件变量对其内部数据结构具有内部锁定,或者使用一些非常小心的无锁算法。

答案 2 :(得分:1)

答案是,有一场比赛,为了消除这场比赛,你必须这样做:

/* atomic op outside of mutex, and then: */

pthread_mutex_lock(&m);
pthread_mutex_unlock(&m);

pthread_cond_signal(&c);

保护数据无关紧要,因为无论如何都不会在调用pthread_cond_signal时保留互斥锁。

通过锁定和解锁互斥锁,您可以创建一个屏障。在信号器具有互斥锁的那个短暂时刻,有一个确定性:没有其他线程具有互斥锁。这意味着没有其他线程正在执行任何关键区域。

这意味着所有线程要么要获取互斥锁以发现您发布的更改,要么他们已经发现了更改并随之运行(释放互斥锁),否则他们没有发现它们正在寻找因为并原子地放弃了互斥体进入睡眠状态(并保证在这种条件下能够很好地等待)。

没有互斥锁定/解锁,您没有同步。信号有时会触发,因为没有看到更改的原子值的线程正在转换到原子睡眠以等待它。

因此,从信令线程的角度来看,这就是互斥体的作用。您可以从其他东西获得访问的原子性,但不能获得同步。

P.S。我之前已经实现了这个逻辑。情况发生在Linux内核中(使用我自己的互斥锁和条件变量)。

在我的情况下,信号器不可能来保存共享数据上的原子操作的互斥锁。为什么?因为信号器在用户空间中执行操作,在内核和用户之间共享的缓冲区内,然后(在某些情况下)对内核进行系统调用以唤醒线程。用户空间只是对缓冲区进行了一些修改,然后如果满足某些条件,它将执行ioctl

所以在ioctl调用中我执行了互斥锁定/解锁操作,然后点击条件变量。这确保了线程不会错过与用户空间发布的最新修改相关的唤醒。

起初我只是有条件变量信号,但没有互斥体的参与它看起来是错误的,所以我稍微推断了一下情况,并意识到互斥锁必须简单地锁定和解锁才能符合同步仪式这消除了失去的唤醒。