c++多线程条件变量 c++多线程为何要使用条件变量详解
发如雪-ty 人气:0先看示例1:
#include <iostream> #include <windows.h> #include <mutex> #include<deque> #include <thread> using namespace std; int nmax = 20; std::deque<int> m_que; std::mutex mymutex; //生产者 void producterex() { int i = 1; while (i<nmax) { //休眠一秒钟 std::this_thread::sleep_for(std::chrono::seconds(1)); std::unique_lock<mutex> lcx(mymutex); m_que.push_back(i); cout << "producted:" << i << endl; lcx.unlock(); i++; } cout << "product thread exit\n"; } //消费者 void consumerex() { int i = 0; while (1) { std::unique_lock<mutex> lcx(mymutex); if (!m_que.empty()) { int i = m_que.back(); m_que.pop_back(); cout << "consumed:" << i << endl; lcx.unlock(); i++; if (i == nmax) { break; } } else { lcx.unlock(); } } cout << "consumerex thread exit\n"; } void main() { std::thread t1(producterex); std::thread t2(consumerex); t1.detach(); cout << "hello"; t2.detach(); cout << " world!\n"; getchar(); system("pause"); }
结果:
可见cpu使用率非常高。高的原因主要在消费者线程中,因为当队列为空的时候它也要执行,做了过多的无用功导致CPU占有率过高,所以下面对进行一个改造让其在空的时候等待200毫秒,相当于增大了轮询间隔周期,应该能降低CPU的占用率。
在这里就贴上消费者的线程,因为其它的都一样。
//消费者 void consumerex() { int i = 0; while (1) { std::unique_lock<mutex> lcx(mymutex); if (!m_que.empty()) { int i = m_que.back(); m_que.pop_back(); cout << "consumed:" << i << endl; lcx.unlock(); i++; if (i == nmax) { break; } } else { lcx.unlock(); std::this_thread::sleep_for(std::chrono::milliseconds(200)); } } cout << "consumerex thread exit\n"; }
结果:
可见CPU占用率一下子下降了。
这里就有一个困难了,那就是如何确定休眠的时间间隔(即轮询间隔周期),如果间隔太短会过多占用CPU资源,如果间隔太长会因无法及时响应造成延误。
这就引入了条件变量来解决该问题:条件变量使用“通知—唤醒”模型,生产者生产出一个数据后通知消费者使用,消费者在未接到通知前处于休眠状态节约CPU资源;当消费者收到通知后,赶紧从休眠状态被唤醒来处理数据,使用了事件驱动模型,在保证不误事儿的情况下尽可能减少无用功降低对资源的消耗。
condition_variable介绍
在C++11中,我们可以使用条件变量(condition_variable)实现多个线程间的同步操作;当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。
成员函数如下:
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:
a.一个线程因等待"条件变量的条件成立"而挂起;
b.另外一个线程使"条件成立",给出信号,从而唤醒被等待的线程。
为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起;通常情况下这个锁是std::mutex,并且管理这个锁 只能是 std::unique_lockstd::mutex RAII模板类。
上面提到的两个步骤,分别是使用以下两个方法实现:
1.等待条件成立使用的是condition_variable类成员wait 、wait_for 或 wait_until。
2.给出信号使用的是condition_variable类成员notify_one或者notify_all函数。
以上两个类型的wait函数都在会阻塞时,自动释放锁权限,即调用unique_lock的成员函数unlock(),以便其他线程能有机会获得锁。这就是条件变量只能和unique_lock一起使用的原因,否则当前线程一直占有锁,线程被阻塞。
虚假唤醒
在正常情况下,wait类型函数返回时要不是因为被唤醒,要不是因为超时才返回,但是在==实际中发现,因此操作系统的原因,wait类型在不满足条件时,它也会返回,这就导致了虚假唤醒。==因此,我们一般都是使用带有谓词参数的wait函数,因为这种(xxx, Predicate pred )类型的函数等价于:
while (!pred()) //while循环,解决了虚假唤醒的问题 { wait(lock); }
原因说明如下:
假设系统不存在虚假唤醒的时,代码形式如下:
if (不满足xxx条件) { //没有虚假唤醒,wait函数可以一直等待,直到被唤醒或者超时,没有问题。 //但实际中却存在虚假唤醒,导致假设不成立,wait不会继续等待,跳出if语句, //提前执行其他代码,流程异常 wait(); } //其他代码 ...
正确的使用方式,使用while语句解决:
while (!(xxx条件) ) { //虚假唤醒发生,由于while循环,再次检查条件是否满足, //否则继续等待,解决虚假唤醒 wait(); } //其他代码 ....
下面看一个使用条件变量的情况:
#include <iostream> #include <windows.h> #include <mutex> #include<deque> #include <thread> #include<condition_variable> using namespace std; int nmax = 10; std::deque<int> m_que; std::mutex mymutex; condition_variable mycv; //生产者 void producterex() { int i = 1; while (i<nmax) { //休眠一秒钟 std::this_thread::sleep_for(std::chrono::seconds(1)); std::unique_lock<mutex> lcx(mymutex); m_que.push_back(i); cout << "producted:" << i << endl; lcx.unlock(); mycv.notify_one(); i++; } cout << "product thread exit\n"; } //消费者 bool m_bflag = false; void consumerex() { int i = 0; bool m_bexit = false; while (!m_bexit) { std::unique_lock<mutex> lcx(mymutex); while (m_que.empty()) { //避免虚假唤醒 mycv.wait(lcx); if (m_bflag) { cout << "consumerex thread exit\n"; m_bexit = true; break; } } if (m_bexit) { break; } int i = m_que.back(); m_que.pop_back(); lcx.unlock(); cout << "consumed:" << i << endl; } cout << "consumerex thread exit\n"; } void main() { std::thread t1(producterex); std::thread t2(consumerex); t1.detach(); cout << "hello"; t2.detach(); cout << " world!\n"; mycv.notify_one(); Sleep(15000); m_que.push_back(100); mycv.notify_one(); Sleep(3000); m_bflag = true; mycv.notify_one();//通知线程退出 getchar(); system("pause"); }
结果:
还可以将mycv.wait(lcx);换一种写法,wait()的第二个参数可以传入一个函数表示检查条件,这里使用lambda函数最为简单,如果这个函数返回的是true,wait()函数不会阻塞会直接返回,如果这个函数返回的是false,wait()函数就会阻塞着等待唤醒,如果被伪唤醒,会继续判断函数返回值。代码示例如下:
#include <iostream> #include <windows.h> #include <mutex> #include<deque> #include <thread> #include<condition_variable> using namespace std; int nmax = 10; std::deque<int> m_que; std::mutex mymutex; condition_variable mycv; //生产者 void producterex() { int i = 1; while (i<nmax) { //休眠一秒钟 std::this_thread::sleep_for(std::chrono::seconds(1)); std::unique_lock<mutex> lcx(mymutex); m_que.push_back(i); cout << "producted:" << i << endl; lcx.unlock(); mycv.notify_one(); i++; } cout << "product thread exit\n"; } //消费者 bool m_bflag = false; void consumerex() { int i = 0; while (1) { std::unique_lock<mutex> lcx(mymutex); mycv.wait(lcx, [](){ //返回false就继续等待 return !m_que.empty(); }); if (m_bflag) { break; } int i = m_que.back(); m_que.pop_back(); lcx.unlock(); cout << "consumed:" << i << endl; } cout << "consumerex thread exit\n"; } void main() { std::thread t1(producterex); std::thread t2(consumerex); t1.detach(); cout << "hello"; t2.detach(); cout << " world!\n"; mycv.notify_one(); Sleep(15000); m_que.push_back(100); mycv.notify_one(); Sleep(3000); m_bflag = true; m_que.push_back(-1); mycv.notify_one();//通知线程退出 getchar(); system("pause"); }
总结
加载全部内容