c++11新特性之条件变量

文章目录

  • 条件变量
    • 1 condition_variable
      • 1.1 成员函数wait()
    • 2 condition_variable_any

条件变量

互斥锁:放行一个线程,阻塞N个线程

条件变量:放心n个线程,阻塞N个线程,主要使用场景:生产者-消费者模型

1 condition_variable

只能使用独占的互斥锁,并且还得配合unique_lock

1.1 成员函数wait()

// ①
void wait (unique_lock<mutex>& lck);
// ②
template <class Predicate>
void wait (unique_lock<mutex>& lck, Predicate pred);
  • 函数1️⃣会直接阻塞线程
  • 函数2️⃣需要调用一个回调函数
    • 可以传一个有名函数的地址,也可以定义一个匿名函数
    • 返回值是false阻塞线程,true继续执行
    • 这个不用while循环,但是原理和while一样
      unique_lock<mutex> locker;
      wait(locker, [=](){
          return taskQueue.size() != maxSize;
      })
      

wait的行为有三步:

  • wait会释放掉获得的锁
  • 阻塞当前线程,等待唤醒
  • 在唤醒之后,重新加锁,并继续向后执行,所以唤醒之后的线程是安全的
    • 当多个线程被同时唤醒,只有第一个会重新上锁,其他的继续被阻塞
    • 阻塞的位置不是哪一行,而是想要重新上锁的阶段

其他用法:

就是两类:不带回调的,需要条件判断,有回调的,判断写回调函数里

template 
cv_status wait_for (unique_lock& lck,
                    const chrono::duration& rel_time);
	
template 
bool wait_for(unique_lock& lck,
               const chrono::duration& rel_time, Predicate pred);

template 
cv_status wait_until (unique_lock& lck,
                      const chrono::time_point& abs_time);

template 
bool wait_until (unique_lock& lck,
                 const chrono::time_point& abs_time, Predicate pred);
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
class TaskQueue
{
public:
    // 生产者存数据
    void put(const int& task)
    {
        // 现在到我工作了,加锁,阻塞其他线程
        unique_lock<mutex> locker(myMx);
        /* 
        为什么是while,而不是if呢?
        如果只判断一次,队列是满的,我解开了锁,阻塞的当前进程,处于等待唤醒的阶段,
        但是不止我一个人是生产者,可能另一个生产者紧接着抢到了锁,也阻塞在这,
        唤醒的时候很多生产者都试图重新加锁进行工作,只有我一个人再次抢到了锁,那么其他人又被阻塞了,
        由于只判断一次,我拿到锁就直接工作了,
        终于我完成了工作,其他被阻塞的线程抢到锁了,但是这时候,由于我的工作,队列又满了,
        但是其他人没有重新判断队列的状态!
        总结就是:
            1. 某个线程的工作导致队列的状态变化了,每次工作前需要重新判断,只有抢到锁并且二次确定队列真的满足条件才能工作
            2. 系统可能会虚假唤醒(系统和硬件问题),如果不循环判断就可能出错
        */
        while(taskQueue.size() == maxSize) // 队列是满的,我生产了没地方存,不生产,解开锁,让别人工作
        {
            // 当不满足生产者的条件的时候,当前线程无法工作,就把这个所有权放出去,阻塞当前线程,让其他线程工作
            // 当其他线程工作完了,满足我的工作条件了,会唤醒我,我再重新加锁,阻塞其他线程
            notEmpty.wait(locker);
        }
        // 队列没有满,我可以生产
        taskQueue.push(task);
        cout << "生产者:" << this_thread::get_id() << "放入数据:" << task << endl;
        // 满足其他线程的工作条件了,唤醒其他线程
        // 我生产完了,我生产了一个产品,通知消费者来消费
        notEmpty.notify_one();
    }

    // 消费者取数据
    void take()
    {
        // 现在到我工作了,加锁,阻塞其他线程
        unique_lock<mutex> locker(myMx);
        while (taskQueue.empty()) // 队列是空的,我没法工作
        {
            // 没法工作就别占着了,把锁解开,让其他人工作,他们做好了会叫我
            notEmpty.wait(locker);
        }
        // 队列不空,满足工作条件,我开始工作
        int task = taskQueue.front();
        taskQueue.pop();
        cout << "消费者:" << this_thread::get_id() << "取走数据:" << task << endl;
        // 我消费完了,我消耗了一个产品,通知生产者干活
        notFull.notify_all();
    }

private:
    int maxSize = 100;
    queue<int> taskQueue;
    mutex myMx;
    condition_variable notFull;
    condition_variable notEmpty;
};

int main()
{
    thread product[5];
    thread consumer[5];
    TaskQueue tq;
    for (int i = 0; i < 5; ++i)
    {
        
        product[i] = thread(&TaskQueue::put, &tq, i + 100);
        consumer[i] = thread(&TaskQueue::take, &tq);
    }

    for (int i = 0; i < 5; ++i)
    {
        product[i].join();
        consumer[i].join();
    }

    return 0;
}

2 condition_variable_any

这个可以使用的锁很多,包括递归互斥锁,超时互斥锁,超时递归互斥锁,以及lock_guard

mutex myMx;
condition_variable_any notFull;
condition_variable_any notEmpty;
    void put(const int &task)
    {
        // 现在到我工作了,加锁,阻塞其他线程
        // lock_guard locker(myMx);
        myMx.lock();
        notFull.wait(myMx, [=]()
                     {
                if (taskQueue.size() == maxSize)
                    cout << "wait" << endl;
                return taskQueue.size() != maxSize; });
        // 队列没有满,我可以生产
        taskQueue.push(task);
        cout << "生产者:" << this_thread::get_id() << "放入数据:" << task << endl;
        // 满足其他线程的工作条件了,唤醒其他线程
        // 我生产完了,我生产了一个产品,通知消费者来消费
        notEmpty.notify_one();
        myMx.unlock();
    }
  • 注意,在wait之前要先上锁,同时不要忘记解锁
  • 更方便的操作就是使用lock_guard自动管理锁,但是他加锁的范围相当大,自行控制使用什么锁,以及生效范围
  • 要注意和condition_variable另一个不同的地方是,他不需要unique_lock来管理,所以要传锁本身,就算使用lock_guard也得传锁对象儿而不是lock_guard对象

你可能感兴趣的:(c++11,c++,开发语言)