条件变量(Condition Variable)的要点:

  1. 条件变量的作用 - 线程同步
    手段: 条件变量控制的是线程的挂起与唤醒,所使用的主要相关函数是 “唤醒” 和 “等待”。
    在C++11中,相关函数是: notify_one/notify_all,
    wait/wait_for/wait_until
    在Linux编程中,相关函数是: pthread_cond_signal/pthread_cond_broadcast,
    pthread_cond_wait/pthread_cond_timedwait
  2. 为什么条件变量经常要配合互斥量 mutex 一起合作?
    因为有时会有许多线程被唤醒,这些线程都会同时要求访问共享资源,因此需要 mutex 来保护共享资源。
  3. wait类函数(如 pthread_cond_wait)的实现做了什么?
    做了3件事:
    1> 将自己放到排队等待条件为真的队列中,然后 unlock (这样别的线程才能拿到锁然后将条件设为真)
    2> 将自己挂起,等待条件为真
    3> 当被唤醒后,立即 lock,以访问共享资源;
    若被别人已经lock了,则继续保持在lock过程中;
    若lock成功了,则退出wait函数,执行后续逻辑;
  4. 什么是虚假唤醒?
    (以下以C++11的相关函数为例说明)
    正常情况下,wait函数只有在2种情况下才会返回:
  • 有线程调 notify ,从而被唤醒
  • wait_for 函数超时
    但是在现实中,有可能因为操作系统的原因,wait函数也会自己返回,尽管条件还并没有被满足。
    因此,一般都是使用带谓词参数的 wait 函数,它相当于如下代码:
while (!pred())  // 若是虚假唤醒,则继续等待
       {
           wait(lock);
       }

附: C++11的wait函数

void wait( std::unique_lock<std::mutex> & lock );

    // Predicate 是谓词函数,可以是普通函数或lambda表达式
    template <class Predicate>
    void wait( std::unique_lock<std::mutex> & lock, Predicate pred );
  1. C++11的 unique_lock 和 lock_guard 都是管理锁的 RAII 性质的辅助类工具,即,都是定义时获得锁,析构时释放锁。
    它们的主要区别是,
  • unique_lock 更加灵活,它可以在需要的时候 lock 或 unlock
  • lock_guard 只能在构造时 lock,在析构时 unlock.
  1. 为什么条件变量使用的经典场景是 生产者-消费者 问题?
    因为在这个问题里会出现条件:
  • 当队列满时,生产者应该被挂起;
  • 当队列空时,消费者应该被挂起;
  • 当队列为空,且在生产者生产之后,消费者应该被通知到;此时需要条件变量,避免消费者的polling;
  • 当队列为满,且在消费者消费之后,生产者应该被通知到;此时需要条件变量,避免生产者的polling;
  • 互斥量保护队列。
  1. 生产者-消费者代码(Linux版)
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER;
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER;

// 消费者
void process_msg(void)
{
    struct msg *mp;
    while(1)
    {
        pthread_mutex_lock(&qlock);
        
        // 1. unlock 2. 阻塞直到条件满足 3. lock
        while(workq == NULL) pthread_cond_wait(&qready, &qlock);
        
        mp = workq;          // dequeue, 即消费
        workq = mp->m_next;  // dequeue, 即消费
        pthread_mutex_unlock(&qlock);  // 解锁,以让生产者拿到锁进行生产
        
        // then process message here...
    }
}

// Producer
void produce_msg(void)
{
    struct msg *mp;
    while(1)
    {
        mp = new msg();  // produce
        
        pthread_mutex_lock(&qlock);  // 当操作临界资源workq的时候需要加锁
        while( is_full(workq) ) pthread_cond_wait(&qready, &qlock);
        
        mp->m_next = workq;  // enqueue, 即生产
        workq = mp;          // enqueue, 即生产
        pthread_mutex_unlock(&qlock);  // 释放锁,然后通知等待条件为真(workq不再为NULL)的进程
        
        pthread_cond_signal(&qready);  // notify all the consumers to acquire the lock
    }
}
  1. C++11 完整代码
#include <iostream>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <queue>
using namespace std;

std::mutex  g_mutex;
std::condition_variable g_cv;

std::deque<int> g_workq;
int g_value = 0;
const int MAX_QUEUE_NUM = 20;


void producer_thread_fn(int tid)
{
    while(true)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        
        std::unique_lock<std::mutex> lock(g_mutex);
        
        g_cv.wait(lock, [](){ return g_workq.size() < MAX_QUEUE_NUM; });
        
        g_value ++;
        
        g_workq.push_back(g_value);
        
        cout << "producer: " << tid << ", data: " << g_value;
        cout << " queue size: " << g_workq.size() << endl;
        
        lock.unlock();
        g_cv.notify_all();
    }
}

void consumer_thread_fn(int tid)
{
    while(true)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(600));
        
        std::unique_lock<std::mutex> lock(g_mutex);
        
        g_cv.wait(lock, [](){ return !g_workq.empty(); });
        
        int data = g_workq.front();
        g_workq.pop_front();
        
        cout << "consumer: " << tid << ", data: " << data ;
        cout << " queue size: " << g_workq.size() << endl;

        lock.unlock();
        g_cv.notify_all();
    }
}


int main()
{
    const int PRODUCER_NUM = 3;
    const int CONSUMER_NUM = 3;

    std::thread Producers[PRODUCER_NUM];
    std::thread Consumers[CONSUMER_NUM];
    
    for (int i=0; i<PRODUCER_NUM; i++)
    {
        Producers[i] = std::thread(producer_thread_fn, i);
    }
    
    for (int i=0; i<CONSUMER_NUM; i++)
    {
        Consumers[i] = std::thread(consumer_thread_fn, i);
    }
    
    for (int i=0; i<PRODUCER_NUM; i++)
    {
        Producers[i].join();
    }
    
    for (int i=0; i<CONSUMER_NUM; i++)
    {
        Consumers[i].join();
    }
    
    return 0;
}

(完)