基本翻译std::condition_variable-cplusplus加参考std::condition_variable-cppreference其他博客。
condition_variable 类是同步原语,能用于阻塞一个线程,或同时阻塞多个线程,直至另一线程修改共享变量(条件)并通知 condition_variable 。
当其wait function
之一(wait
、wait_for
、wait_until
)被调用时,其使用一个unique_lock
去阻塞该线程,该线程将一直阻塞直到被另一个线程通过调用同一个condition_variable
对象的 notification function
唤醒。
std::condition_variable
只可与 std::unique_lock
一同使用;
condition_variable
不可复制构造 (CopyConstructible
) 、可移动构造 (MoveConstructible
) 、可复制赋值 (CopyAssignable
) 或可移动赋值 (MoveAssignable
) 。
其成员函数比较少,包括三大类:
先用一个例子大致了解一下:
#include
#include
#include //mutex unique_lock
#include
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()
{
ready = true;
cv.notify_all();
}
int main()
{
std::thread threads[10];
for(int i=0; i<10; i++)
threads[i] = std::thread(print_id, i+1);
go(); //begin race
for(auto& th : threads){
th.join();
}
return 0;
}
void wait (unique_lock<mutex>& lck);
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);
第一种方式的wait导致当前线程阻塞直至条件变量被通知,或虚假唤醒(spurious wake-up)发生。
第二种方式的wait则在不满足条件之前会一直在while循环中,第二种方式等效为:
while (!pred()) {
wait(lock);
}
pred可以是一个函数或者lambda表达式。
上面这段话的大意是说:
当阻塞线程时,wait函数会自动调用lck.unlock(),允许其他锁住的线程继续;
一旦被通知,就不在阻塞并调用lck.lock()获得锁,然后函数返回。(阻塞的线程释放锁,不阻塞的线程获得锁)
但是wait函数可能会引起虚假唤醒,使用者应该保证被唤醒的条件确实被满足。
如果使用了pred,该函数之后在pred返回false时阻塞,而且notification函数只有在pred变为true时才能不再阻塞该线程,这可以有效地应对虚假唤醒现象。
看一个wait使用的例子:
#include
#include //mutex & unique_lock
#include
#include
using namespace std;
mutex mtx;
condition_variable cv;
int cargo = 0;
bool pred() { return cargo != 0; }
void consumer(int n)
{
for (size_t i = 0; i < n; i++)
{
unique_lock<mutex> lck(mtx);
cv.wait(lck, pred);//阻塞直到pred满足, 即 cargo != 0, wait 会调用mtx.unlock()
cout << cargo << "\n";
cargo = 0; //再将cargo置为0, 导致主线程中的while可以执行
}
}
int main()
{
thread consume(consumer, 10);
for(int i=0; i<10; i++){
while(pred()){ //不满足条件则yield, cargo != 0则阻塞,等待consumer将cargo设置为0
this_thread::yield();
}
unique_lock<mutex> lck(mtx);//调用mtx.lock()
cargo = i+1;
cv.notify_one();
}
consume.join();
return 0;
}
在main函数中,使得cargo累加,导致pred返回true,然后consumer函数中的wait不再阻塞,输出cargo,然后consumer函数将cargo再次设置为0,进行下次循环。
输出结果:
刚开始时一直比较懵,main函数中没有加unique_lock
这句话;
这就导致在consumer
函数中,先执行unique_lock
调用mtx.lock();
,该线程获得锁mtx
;
然后执行cv.wait(lck, pred);
调用lck.unlock()
(相当于调用mtx.unlock()
)使得锁mtx
被释放。
然后,for循环结束,lck被析构,再次执行lck.unlock();
,相当于又一次调用了mtx.unlock()
,mtx被unlock了两次,这种行为是未定义的,然后程序卡死了。
在main函数中加入unique_lock
就可以了。
假设main函数中的unique_lock
先获得锁,那么consumer中的unique_lock
就会阻塞,直到main函数运行完cv.notify_one();
。main中的lck析构,mtx被释放,consumer得以执行。
假设consumer中的unique_lock
先获得锁,main函数中的unique_lock
就会阻塞,当consumer执行完wait函数后,锁被释放,main函数得以执行,但是由于consumer中wait的条件pred不满足,导致consumer阻塞直到main函数将cargo的值设置为非0.
template <class Clock, class Duration>
cv_status wait_until (unique_lock<mutex>& lck,
const chrono::time_point<Clock,Duration>& abs_time);
template <class Clock, class Duration, class Predicate>
bool wait_until (unique_lock<mutex>& lck,
const chrono::time_point<Clock,Duration>& abs_time,
Predicate pred);
wait_until 导致当前线程阻塞直至通知条件变量、抵达指定时间或虚假唤醒发生,如果调用的是包含Predicate pred的函数,还需要等到条件满足。
包含Predicate pred的wait_until相当于:
while (!pred())
if ( wait_until(lck,abs_time) == cv_status::timeout)
return pred();
return true;
返回值说明:
当调用的是不包含Predicate pred的wait_until时,返回一个cv_status
类型的对象,时间到则返回cv_status::timeout
,否则返回cv_status::no_timeout
。
cv_status是枚举类型:enum class cv_status { no_timeout, timeout };
当调用的是包含Predicate pred的wait_until时,返回pred(),是一个bool。
unconditional (1)
template <class Rep, class Period>
cv_status wait_for (unique_lock<mutex>& lck,
const chrono::duration<Rep,Period>& rel_time);
predicate (2)
template <class Rep, class Period, class Predicate>
bool wait_for (unique_lock<mutex>& lck,
const chrono::duration<Rep,Period>& rel_time, Predicate pred);
第二个参数是设置的一个时间,第三个参数是需要满足的条件。
该函数直到被notify或者时间到的时候才会返回。
执行该函数会调用lck.unlock(),被通知或者时间到时会执行lck.lock();
第二种predicate ,相当于执行:
return wait_until (lck, chrono::steady_clock::now() + rel_time, std::move(pred));
例子:
:cin >> value;
cv.notify_one();
}
int main()
{
std::cout << "please input value, i will output . \n";
std::thread th(read_value);
std::mutex mtx;
void notify_one() noexcept;
void notify_all() noexcept;
If no threads are waiting, the function does nothing.
代码来自:C++11条件变量使用详解
#include
#include
#include
#include
#include
#include
using namespace std;
mutex mtx;
condition_variable cv;
deque<int> datadeque; //缓冲区
int gval = 0; //数据
const int BUFFER_SIZE = 30;//缓冲区大小
const int PRODUCER_NUM = 3;//生产者线程数量
const int CONSUMER_NUM = 3;//消费者线程数量
void producer(int id)
{
while (true)
{
this_thread::sleep_for(chrono::milliseconds(100));
unique_lock<mutex> lck(mtx);
cv.wait(lck, [](){ return datadeque.size() <= BUFFER_SIZE; });//当队列的大小达到BUFFER_SIZE时阻塞
gval++;
datadeque.push_back(gval);
cout << "producer thread " << id << " produced data " << gval << ", deque size " << datadeque.size() << endl;
cv.notify_all();
}
}
void consumer(int id)
{
while (true)
{
this_thread::sleep_for(chrono::milliseconds(100));
unique_lock<mutex> lck(mtx);//lock
cv.wait(lck, [](){ return !datadeque.empty(); } );//当缓冲队列为空时阻塞 unlock
int front = datadeque.front();
datadeque.pop_front();
cout << "\tconsumer thread " << id << " consumed data " << front << ", deque size " << datadeque.size() << endl;
cv.notify_all();
}
}
int main()
{
thread pro_th[PRODUCER_NUM];
thread con_th[CONSUMER_NUM];
//生产者线程
for(int i=0; i<PRODUCER_NUM; i++)
pro_th[i] = thread(producer, i+1);
for(int i=0; i<CONSUMER_NUM; i++)
con_th[i] = thread(consumer, i+1);
//消费者线程
for(int i=0; i<PRODUCER_NUM; i++)
pro_th[i].join();
for(int i=0; i<CONSUMER_NUM; i++)
con_th[i].join();
return 0;
}
条件变量的虚假唤醒(spurious wakeups)问题
https://www.jianshu.com/p/0eff666a4875