[C++11 多线程同步] --- 条件变量

Posted Overboom

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C++11 多线程同步] --- 条件变量相关的知识,希望对你有一定的参考价值。

1 std::condition_variable 类介绍

当 std::condition_variable 对象的某个 wait 函数被调用的时候,它使用 std::unique_lock(通过 std::mutex) 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的 std::condition_variable 对象上调用了 notification 函数来唤醒当前线程。

std::condition_variable 对象通常使用 std::unique_lock< std::mutex> 来等待,如果需要使用另外的 lockable 类型,可以使用 std::condition_variable_any 类。

std::condition_variable和 std::condition_variable_any 类对比:
相同点:两者都能与std::mutex一起使用。
不同点:前者仅限于与 std::mutex 一起工作,而后者可以和任何满足最低标准的互斥量一起工作,从而加上了_any的后缀。condition_variable_any会产生额外的开销。
一般只推荐使用condition_variable。除非对灵活性有硬性要求,才会考虑condition_variable_any。

2 condition_variable member function介绍

2.1 构造函数

default (1)condition_variable()
copy [deleted] (2)condition_variable (const condition_variable&) = delete;

std::condition_variable 的拷贝构造函数被禁用,只提供了默认构造函数。

2.2 std::condition_variable::wait()

unconditional(1)void wait (unique_lock& lck);
predicatate(2)template void wait (unique_lock& lck, Predicate pred);

std::condition_variable 提供了两种 wait() 函数。当前线程调用 wait() 后将被阻塞(此时当前线程应该获得了锁(mutex),不妨设获得锁 lck),直到另外某个线程调用 notify_* 唤醒了当前线程。

在线程被阻塞时,该函数会自动调用 lck.unlock() 释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。另外,一旦当前线程获得通知(notified,通常是另外某个线程调用 notify_* 唤醒了当前线程),wait() 函数也是自动调用 lck.lock(),使得 lck 的状态和 wait 函数被调用时相同。

代码段1:unconditional(1) wait方法的使用

#include <iostream>                 // std::cout
#include <thread>                   // std::thread
#include <mutex>                    // std::mutex, std::unique_lock
#include <condition_variable>       // std::condition_variable
#include <windows.h>

std::mutex mtx; // 全局互斥锁.
std::condition_variable cv; // 全局条件变量.
bool ready = false; // 全局标志位.

void thread1()

	std::cout << "thread1 start" << std::endl;

	// step1:调用wait前,对当前线程加锁,达到mutex
	std::unique_lock <std::mutex> lck(mtx);
	std::cout << "before call wait" << std::endl;

	cv.wait(lck);
	// step2:调用wait后,线程会被阻塞
	// step2: 同时系统自动调用mutex.unlock释放锁

	// setp3: 一旦调用notify唤醒当前wait,系统也会主动调用mutex.lock(),使得mutex的状态跟刚调用wait时那样加锁
	// 线程被唤醒, 继续往下执行打印
	std::cout << "thread1 finished" << '\\n';


void thread2()

	std::cout << "thread2 start" << std::endl;

	std::unique_lock <std::mutex> lck(mtx);
	std::cout << "before call notify, we sleep 15s" << std::endl;
	std::cout << "you can oberserve in terminal, thread1 is blocking in wait" << std::endl;
	Sleep(15000);
	cv.notify_one(); // 唤醒线程.


int main()

	std::thread th1(thread1);
	std::thread th2(thread2);

	th1.join();
	th2.join();

	system("pause");
	return 0;


执行结果如下:

代码段2: predicatate(2) wait方法的使用

在第二种情况下(即设置了 Predicate),只有当 pred 条件为 false 时调用 wait() 才会阻塞当前线程,并且在收到其他线程的通知后只有当 pred 为 true 时才会被解除阻塞。因此第二种情况类似以下代码:

while (!pred()) 
	wait(lck);
#include <iostream>                // std::cout
#include <thread>                // std::thread, std::this_thread::yield
#include <mutex>                // std::mutex, std::unique_lock
#include <condition_variable>    // std::condition_variable

std::mutex mtx;
std::condition_variable cv;

int cargo = 0;
bool shipment_available()

    return cargo != 0;


// 消费者线程.
void consume(int n)

    for (int i = 0; i < n; ++i) 
        std::unique_lock <std::mutex> lck(mtx);
        cv.wait(lck, shipment_available);
        std::cout << cargo << '\\n';
        cargo = 0;
    


int main()

    std::thread consumer_thread(consume, 10); // 消费者线程.

    // 主线程为生产者线程, 生产 10 个物品.
    for (int i = 0; i < 10; ++i) 
        while (shipment_available())
            std::this_thread::yield();
        std::unique_lock <std::mutex> lck(mtx);
        cargo = i + 1;
        cv.notify_one();
    

    consumer_thread.join();

    return 0;

执行结果如下:

2.3 std::condition_variable::wait_for / std::condition_variable::wait_until

与std::condition_variable::wait() 相似,只是 wait_for能够指定一个时间段,在当前线程收到通知或者指定的时间 rel_time 超时之前。该线程都会处于堵塞状态。而一旦超时或者收到了其它线程的通知,wait_for返回,剩下的处理步骤和 wait()相似。
与 std::condition_variable::wait_for 相似,可是wait_until能够指定一个时间点,在当前线程收到通知或者指定的时间点 abs_time超时之前,该线程都会处于堵塞状态。而一旦超时或者收到了其它线程的通知,wait_until返回。剩下的处理步骤和 wait_until() 相似。

2.4 std::condition_variable::notify_one() / std::condition_variable::notify_all()

otify_one():没有参数、没有返回值。
解除阻塞当前正在等待此条件的线程之一。如果没有线程在等待,则还函数不执行任何操作。如果超过一个,不会指定具体哪一线程。

看一段示例代码:

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

std::mutex mtx;
std::condition_variable produce,consume;

int cargo = 0;     // shared value by producers and consumers

void consumer () 
  std::unique_lock<std::mutex> lck(mtx);
  while (cargo==0) consume.wait(lck);
  std::cout << cargo << '\\n';
  cargo=0;
  produce.notify_one();


void producer (int id) 
  std::unique_lock<std::mutex> lck(mtx);
  while (cargo!=0) produce.wait(lck);
  cargo = id;
  consume.notify_one();


int main ()

  std::thread consumers[10],producers[10];
  // spawn 10 consumers and 10 producers:
  for (int i=0; i<10; ++i) 
    consumers[i] = std::thread(consumer);
    producers[i] = std::thread(producer,i+1);
  

  // join them back:
  for (int i=0; i<10; ++i) 
    producers[i].join();
    consumers[i].join();
  

  return 0;

运行结果如下:

3 std::condition_variable_any 介绍

与 std::condition_variable 类似,只不过 std::condition_variable_any 的 wait 函数可以接受任何 lockable 参数,而 std::condition_variable 只能接受 std::unique_lockstd::mutex 类型的参数,除此以外,和 std::condition_variable 几乎完全一样。

4 std::cv_status 枚举类型介绍

cv_status::timeoutwait_for 或者 wait_until 超时。
cv_status::no_timeoutwait_for 或者 wait_until 没有超时,即在规定的时间段内线程收到了通知。

5 std::notify_all_at_thread_exit介绍

函数原型为:

void notify_all_at_thread_exit (condition_variable& cond, unique_lock<mutex> lck);

当调用该函数的线程退出时,所有在 cond 条件变量上等待的线程都会收到通知。请看下例

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable

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);
  std::notify_all_at_thread_exit(cv,std::move(lck));
  ready = true;


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";

  std::thread(go).detach();   // go!

  for (auto& th : threads) th.join();

  return 0;

好了,到此为止,<condition_variable> 头文件中的两个条件变量类(std::condition_variable 和 std::condition_variable_any)、枚举类型(std::cv_status)、以及辅助函数(std::notify_all_at_thread_exit())都已经介绍完了

参看连接:
https://www.jianshu.com/p/a31d4fb5594f

https://www.cnblogs.com/wangshaowei/p/9593201.html

https://www.cnblogs.com/GuoXinxin/p/11675053.html

https://blog.csdn.net/c_base_jin/article/details/89741247

https://www.cnblogs.com/wangshaowei/p/9593201.html

以上是关于[C++11 多线程同步] --- 条件变量的主要内容,如果未能解决你的问题,请参考以下文章

[C++11 多线程同步] --- 条件变量

[C++11 多线程同步] --- 条件变量的那些坑条件变量信号丢失和条件变量虚假唤醒(spurious wakeup)

[C++11 多线程同步] --- 条件变量的那些坑条件变量信号丢失和条件变量虚假唤醒(spurious wakeup)

[C++11 多线程同步] --- 条件变量的那些坑条件变量信号丢失和条件变量虚假唤醒(spurious wakeup)

[多线程]C++11多线程-条件变量(std::condition_variable)

并发编程多线程程序同步策略