C++11多线程:condition_variable头文件

<condition_variable>头文件主要包含了与条件变量相关的功能类:condition_variablecondition_variable_any;枚举类:cv_status;以及功能函数notify_all_at_thread_exit
在这里插入图片描述

1. condition_variable

条件变量可以用来在特定情况下阻塞线程,直到收到通知重新运行线程,该过程是通过unique_lock<mutex>实现的。当条件不满足时,通过调用unique_lock的某个等待函数使当前进程阻塞;等到条件满足后,通过在其他线程中调用同一条件变量的唤醒函数通知当前线程重新启动。若想使用unique_lock以外的可锁定类型,可以参见condition_variable_any类。
在这里插入图片描述
示例:

// condition_variable example
#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);
  ready = true;
  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();                       // go!

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

  return 0;
}

输出(线程顺序可能不同):

10 threads ready to race...
thread 2
thread 0
thread 9
thread 4
thread 6
thread 8
thread 7
thread 5
thread 3
thread 1

(1). (constructor)

在这里插入图片描述
创建一个condition_variable对象,对象不可复制不可转移。

(2). wait

在这里插入图片描述
wait函数分为两种:(1)无条件wait (2)带条件wait

(1) 无条件wait
阻塞当前线程(此前lck需要先将mutex上锁),直到收到通知重新唤醒线程。线程阻塞以后,函数自动调用lck.unlock()释放mutex,使其他等待该互斥量的线程得以继续执行。一旦收到唤醒通知,函数会唤醒当前线程,并通过lck.lock()重新上锁(当mutex被占用时会发生二次阻塞),上锁成功后,lck恢复到函数调用以前的状态,函数返回。

唤醒通知通常是在其他线程中通过调用notify_one或者notify_all实现。使用这些函数时需要确保唤醒条件得到满足,避免出现虚假唤醒通知。

(2) 带条件wait
该版本wait带有判断体pred,只有当pred返回false时线程发生阻塞,并且在收到唤醒通知后,只有pred返回true线程才会被唤醒,这对于防止虚假唤醒通知非常有效。带条件的wait调用等同于:while(!pred()) wait(lck);

示例:

// condition_variable::wait (with predicate)
#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);
    // consume:
    std::cout << cargo << '\n';
    cargo=0;
  }
}

int main ()
{
  std::thread consumer_thread (consume,10);

  // produce 10 items when needed:
  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;
}

输出:

1
2
3
4
5
6
7
8
9
10

上例中,cargo作为判断条件。cargo=0时,consume线程阻塞,主线程运行,主线程修改cargo后发出唤醒通知,同时自己yield(让出cpu,并非阻塞)。consume线程被唤醒后输出cargo并重新为其赋值0,阻塞自己等待主线程唤醒。cargo=0后主线程继续运行修改其值并发出唤醒通知。两个线程交替运行,直到输出10个结果。

(3). wait_for和wait_until

工作原理和wait类似,只是分别给wait的时间段和时间点设了限制。区别类似于timed_mutex中的try_lock_fortry_lock_until与mutex中lock的区别。

(4). notify_one

唤醒一个等待该条件变量的阻塞线程。如果没有等待线程,函数不作为,如果有多个等待线程,不明确指定哪个线程被唤醒,即随机唤醒。

示例:

// condition_variable::notify_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;
}

输出(顺序可能不同):

1
2
3
4
5
6
7
8
9
10

上例中,设置了两个条件变量:produce和consume分别作用于生产者线程和消费者线程。cargo代表资源,cargo=0时,消费者阻塞,生产者开始制造,制造完成cargo!=0,此时通知消费者消费;cargo!=0时,生产者阻塞,消费者开始消费,消费完成cargo=0,此时通知生产者生产。

(5). notify_all

唤醒所有等待该条件变量的阻塞线程。如果没有等待线程,函数不作为。

与notufy_one的区别:
notify_one是随机唤醒一个等待中的线程获取锁,不能保证唤醒线程就是想要的线程。notify_all是唤醒所有等待中的线程去竞争锁。线程等待的是notify_*函数而不是锁,notify_one调用后,一个线程被唤醒去获取锁,线程执行完毕释放锁,若没有新的notify_*函数被调用,即使锁空闲,依然没有线程被唤醒。若调用notify_all,所有的等待线程都被唤醒,公平竞争锁,直到所有的线程运行完成。

示例:

// condition_variable::notify_all
#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);
  ready = true;
  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();                       // go!

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

  return 0;
}

输出:

10 threads ready to race...
thread 6
thread 2
thread 5
thread 3
thread 4
thread 1
thread 7
thread 0
thread 9
thread 8

2. condition_variable_any

和condition_variable基本一样,唯一的区别在于condition_variable_any可以选用任何类型的锁实现加解锁(condition_variable只能选用unique_lock<mutex>)。除此之外,和condition_variable完全相同。

3. cv_status

枚举类型,指示函数是否由于超时而返回。该类型是condition_variable和condition_variable_any对象中函数wait_forwait_until的返回类型。

定义:enum class cv_status { no_timeout, timeout };

cv_status::no_timeout :函数在规定时间内返回(例如:被notufy_*唤醒)。
cv_status::timeout:函数因超时返回。

4. notufy_all_at_thread_exit

void notify_all_at_thread_exit (condition_variable& cond, unique_lock lck); //调用线程退出时,唤醒所有被cond阻塞的线程。

函数会获取lck中mutex的拥有权,转为函数内部存储,在线程退出时解锁释放,释放之后到退出之前会唤醒所有cond阻塞的线程。等同于以下语句:

lck.unlock();
cond.notify_all();

示例:

// notify_all_at_thread_exit
#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;
}

输出(顺序可能不同):

10 threads ready to race...
thread 9
thread 0
thread 7
thread 2
thread 5
thread 4
thread 6
thread 8
thread 3
thread 1

参考

[1]. http://www.cplusplus.com/reference/condition_variable/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值