pthread条件变量深入解析

用条件变量实现事件等待器的正确与错误做法 提到了8种 基于 linux pthread 条件变量实现的 Waiter classes,并分析了几种错误实现的错误之处。本文进一步分析一下几种正确实现的程序行为,加深对Linux pthread 条件变量的理解。

下面给出一个可以用于single waiter的WaiterClass的正确实现。

class Waiter : private WaiterBase
{
 public:
  void wait()
  {
    CHECK_SUCCESS(pthread_mutex_lock(&mutex_));
    while (!signaled_)
    {
      CHECK_SUCCESS(pthread_cond_wait(&cond_, &mutex_));
    }
    CHECK_SUCCESS(pthread_mutex_unlock(&mutex_));
  }

  void signal()
  {
    CHECK_SUCCESS(pthread_mutex_lock(&mutex_));    // 0
    signaled_ = true;                              // 1
    CHECK_SUCCESS(pthread_mutex_unlock(&mutex_));  // 2
    CHECK_SUCCESS(pthread_cond_signal(&cond_));    // 3
  }

 private:
  bool signaled_ = false;
};

要实现一个正确的Waiter Class, 由 1 可知 wait() 函数的指令顺序必须如上所示。但signal() 函数可以有几种不同的实现,即代码行 1,2,3可以有几种不同的顺序组合,使得Waiter Class是正确的。这几种组合包括了1 给出的几种正确实现。

首先对 代码行1,2,3 给出所有可能的排列形式,然后一一说明其是否正确。
a. 1-2-3
b. 1-3-2
c. 2-1-3
d. 2-3-1
e. 3-1-2
f. 3-2-1

要分析以上6种实现的对错,先要了解一下 pthread_cond_wait 和 pthread_cond_signal 的内部流程。

假设等待信号的线程为A,发出信号的线程为B。

线程A,pthread_cond_wait()内部包括以下流程:

  1. 将 waiter 加入cond 的 _wseq 队列 (分为G1 G2两个组)
  2. 释放mutex
  3. 自旋等待,检查 __g_signals,自旋次数结束,进入 futex_wait,休眠

线程B,pthread_cond_signal()包括以下流程:

  1. 检查 cond __wseq,若没有等待者则直接返回。
  2. 有等待者,检查是否需要切换组(例如首次调用 wait 后 G1 为空,G2有一个等待者,则首次调用 signal 后需要将 G2 切换为 G1),递增 __g_signals,递减 __g_size(未唤醒的 waiters 个数),再调用 futex_wake。

signal 将一直唤醒G1组的 waiters 直到 G1 所有的 waiters 都被唤醒。若此过程种有新到达的waiter, 则存入G2 组(之后到达的 waiter 也存入G2)。G2组会在下次signal 调用时转为G1组,因此signal永远只唤醒G1 组的 waiters。(参考)

有了以上的细节,可以很容易的得出问题的结论。

  1. 首先应当排除e和f。ef拿到锁又马上释放锁,1和2都不被锁保护,1,2执行的时机可以任意穿插到wait()函数各语句zhi’jian,从而语句2发出的信号很可能会丢失
  2. d也是错误的。一种导致线程A饿死的时序:2发信号,唤醒线程A,3释放锁,A获得锁,判断布尔值,条件为真,再次进入等待,一直休眠
  3. ac是正确的。不管发信号时是否修改了布尔值,被唤醒的A线程始终无法得到mutex,直到3释放锁,futex_wake通知线程A, A才可以拿到锁,从pthread_cond_wait返回,继续向下执行,此时布尔值已经改变,条件测试为假,跳出循环,继续执行。事实上,由于编译器乱序和CPU 乱序,ac有可能运行的次序是相同的。
  4. b也是正确的。B线程在修改布尔变量后释放锁,此时线程A依然处在休眠状态,不知道发生的一切,只有当B发信号唤醒A时,A才可以看到这一切,成功获得锁,进而继续向下执行。

总结发现,只要布尔值的修改是在释放锁的操作之前,就能保证其正确性。

    原文作者:jzzlee
    原文地址: https://segmentfault.com/a/1190000013246267
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞