C++标准库中的底层同步原语。理解std::condition_variable,着重就在于理解std::condition_variable::wait()这个函数,签名为void(std::unique_lock<std::mutex>& lk, Predicate pred)Predicate代表谓词。


wait 的作用

一句话解释:阻塞当前线程,直至被其他线程通过notify_all()或者notify_one()唤醒,并且满足谓词条件。notify*()也是std::condition_variable的成员函数。

如何调用

线程在调用`wait()`之前必须先获得锁,也就是说,必须是如下所示配套的调用:
1
2
3
4
5
std::condition_variable cv;
std::mutex mutex_;

std::unique_lock<std::mutex> lk(mutex_); // 争取获得锁
cond.wait(lk, []{/*谓词*/}); // OK, 已经获得锁了,可以开始wait

内部流程

如图 1 所示。加锁之后,线程 A 独占资源,然后判断谓词是否满足。

  • 满足谓词:OK,跳出 wait,执行程序的其他部分;
  • 不满足谓词:将锁解开,并且线程 A 阻塞,等待其他线程将其唤醒;

wait()流程

虚假唤醒

spurious wakeup。为避免这种情况,系统在实现wait的时候使用while循环,如下所示为等价形式:

1
2
3
while(!pred()){
wait(lk);
}

应用场景

直接拿 leetcode 多线程中的一道题目来阐述。

我们提供一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class FooBar {
public void foo() {
  for (int i = 0; i < n; i++) {
    print("foo");
}
}
public void bar() {
  for (int i = 0; i < n; i++) {
    print("bar");
  }
}
}
两个不同的线程将会共用一个 FooBar 实例。
其中一个线程将会调用 foo() 方法,另一个线程将会调用 bar() 方法。
请设计修改程序,以确保 "foobar" 被输出 n 次。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/print-foobar-alternately
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

简单来说,就是希望线程 A 和线程 B 能够交替进行 print。显然,这涉及到了进程间的通信。

运用condition_variable进行线程通信的原理就是:设定一个变量,不同的线程需要针对该变量满足不同的条件时,才能够继续执行,否则阻塞;执行完毕后必须更改变量,并且notify别的线程。

了解了原理,那就好办了,我们就设一个变量int index;,当其为偶数时,执行线程 A,否则执行线程 B。需要注意的是:使用 RAII 模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

class FooBar {
int index;
std::mutex mutex_;
std::condition_variable cv;
private:
int n;

public:
FooBar(int n): index(0) {
this->n = n;
}

void foo(function<void()> printFoo) {

for (int i = 0; i < n; i++) {
{
std::unique_lock<std::mutex> lk(mutex_);
cv.wait(lk, [this](){return index%2==0;});

// printFoo() outputs "foo". Do not change or remove this line.
printFoo();
++index;
cv.notify_one();
}
}
}

void bar(function<void()> printBar) {

for (int i = 0; i < n; i++) {
{
std::unique_lock<std::mutex> lk(mutex_);
cv.wait(lk, [this](){return index%2!=0;});

// printBar() outputs "bar". Do not change or remove this line.
printBar();
++index;
cv.notify_one();
}
}
}
};

替代信号量

“Semaphore has no notion of ownership.” semaphore 作为底层原语,其并不完备,也不是必备的。semaphore 的功能完全可以通过 condition_variable+mutex 来替代。