多线程—条件变量condition_variable
https://blog.csdn.net/feng__shuai/article/details/108546003
基本介绍
在多线程编程中有一个条件变量,在头文件中condition_variable,常用在设计线程池的时候使用,该条件变量需要配合mutex才能使用
std::mutex mtx;
std::condition_variable cv;
void print_id (int id)
{
std::unique_lock<std::mutex> lck(mtx);// 首先执行到这一句的第一个线程会lck.lock(), 其他线程则会卡在这一句;
cv.wait(lck);
/*
这个函数比较有意思,当执行到这一行的时候,他会lck.unlock(), 然后条件变量接替开始阻塞,这样第二个线程就会
执行上一句lck初始化,然后所有线程都会走一遍lck.lock()->lck.unlock()->阻塞,然后所有线程都停留在这个位置,
等待通知,最先得到通知的线程会调用lck.lock()(至于谁先得到通知线程去竞争了),这样就保证下面这句命令操作的原子性
*/
std::cout << "thread " << id << '\n';
}//类似于只能指针,lck自动unlock
void go()
{
std::unique_lock<std::mutex> lck(mtx);
cv.notify_all();
}
int main ()
{
std::thread threads[10];
// spawn 10 threads:
for (int i=0; i<10; ++i) threads[i] = std::thread(print_id,i);
std::cout << "10 threads ready to race...\n";
go();//主线程lck.lock()->通知所有wait->
for (auto& th : threads) th.join();
return 0;
}
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
上面是个基本的操作,但是常用的方式不是这样,在cplusplus的demo中会设置一个全局标志符,类似于下面这样,但是一开始的时候
我是很好奇为啥要搞一个ready标志符,后来才知道是为了虚假唤醒:
这一块还有一个注意事项:notiff_all必须晚于wait才有效果,条件变量的目的是提供同步机制
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_id (int id)
{
std::unique_lock<std::mutex> lck(mtx);
while (!ready) cv.wait(lck);
std::cout << "thread " << id << '\n';
}
void go() {
std::unique_lock<std::mutex> lck(mtx);
ready = true;
cv.notify_all();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
虚假唤醒
std::mutex mtx;
std::condition_variable cv;
void process
{
std::unique_lock<std::mutex> lck(mtx);
while(queue.empty())
{
cond.wait();
/*
等效于下面代码
mtx.unlock();
while(!singnal) ;
mtx.lock();
*/
}
x=queue.pop();
lck.unlock();//显示解锁互斥量
do_something(x);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
举个例子,我们现在有一个生产者-消费者队列和三个线程。
1号线程从队列中获取了一个元素,此时队列变为空。
2号线程也想从队列中获取一个元素,但此时队列为空,2号线程便只能进入阻塞(cond.wait()),等待队列非空。
这时,3号线程将一个元素入队,并调用cond.notify()唤醒条件变量。
处于等待状态的2号线程接收到3号线程的唤醒信号,便准备解除阻塞状态,执行接下来的任务(获取队列中的元素)。
然而可能出现这样的情况:当2号线程准备获得队列的锁,去获取队列中的元素时,此时1号线程刚好执行完之前的元素操作,返回再去求队列中的元素,1号线程便获得队列的锁,检查到队列非空,就获取到了3号线程刚刚入队的元素,然后释放队列锁。
等到2号线程获得队列锁,判断发现队列仍为空,1号线程“偷走了”这个元素,所以对于2号线程而言,这次唤醒就是“虚假”的,它需要再次等待队列非空。
结论
可以看出通过while就有了双保险,如果queue被人偷了,则继续进入循环,没被偷的话就不进入循环往下走了,起到了双保险的效果,这是一种经验套路写法,背住就行了。
浙公网安备 33010602011771号