条件变量-condition_variable

一、condition_variable条件变量简介

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

condition_variable成员函数:

  • condition_variable: 不可拷贝不可赋值;
  • notify_one():唤醒一个等待的线程;
  • notify_all():唤醒所有等待的线程;
  • wait():阻塞等待直到被唤醒;
  • wait_for():阻塞等待被唤醒,或者超时;
  • wait_until():阻塞等待被唤醒,或者到某个时间点。

二、demo

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using std::mutex;
using std::condition_variable;
using std::unique_lock;
using std::thread;
using std::cout;
using std::endl;
mutex mtx;// 全局互斥锁
condition_variable cv;// 全局条件变量
bool ready = false;// 全局标志位
void do_print_id(int id) 
{
    /**********************************************************
    *我们发现,在条件变量cv的wait函数中,我们传入了一个lock
    *参数,为什么要用锁呢?因为ready为临界变量,主线程中会
    *“写”它,子线程中要“读”它,这样就产生了数据竞争,并且这
    *个竞争很危险。
    *假如现在执行完条件判断后,时间片轮转,该子线程暂停执行。
    *而恰好在这时,主线程修改了条件,并调用了cv.notify_all()
    *函数。这种情况下,该子线程是收不到通知的,因为它还没挂起。
    *等下一次调度子线程时,子线程接着执行2将自己挂起。
    *但现在主线程中的notify早已经调用过了,不会再调第二次了,
    *所以该子线程永远也无法唤醒了。为了解决上面的情况,就要使
    *用某种同步手段来给线程加锁。而c++11的condition_variable
    *选择了用unique_lock<Mutex>来配合完成这个功能。并且我们只
    *需要加锁,条件变量在挂起线程时,会调用原子操作来解锁。
    **********************************************************/
    unique_lock<mutex> lck(mtx);
    while (!ready)// 如果标志位不为 true, 则等待... 
    { 
        // 当前线程被阻塞。 
        cv.wait(lck);
    }
    // 如果线程被唤醒, 则继续往下执行打印线程编号id。 
    cout << "thread " << id << endl;
}
void go() 
{
    unique_lock<mutex> lck(mtx);
    ready = true;// 设置全局标志位为 true。 
    cv.notify_all();// 唤醒所有线程。 
}
int main() 
{
    thread threads[10];
    for (int i = 0; i < 10; ++i) 
    {
        threads[i] = thread(do_print_id, i);//所有线程都被挂起。 
    }
    cout << "10 threads ready to race...\n";
    go();//唤醒所有线程。 
    for (auto &th : threads) 
    {
        th.join();
    }
    system("pause");
    return EXIT_SUCCESS;
}

三、同步队列的实现

/***********************************************************************
*同步队列要求只能有一个任务对其进行操作(因此无需再对其使用同步操作,同
步队列内部是同步的)。当队列是空时,会导致取该队列的线程阻塞;当队列满
(设置固定大小的队列)时,会导致写该队列的线程阻塞。 
************************************************************************/
#include <mutex>
#include <thread>
#include <condition_variable>
#include <chrono>
#include <iostream>
#include <list>
#include <vector>
#include <memory>
using namespace std;

template<typename T>
class SynQueue
{
public:
    //构造函数
    SynQueue(int MaxSize) : m_maxsize(MaxSize) { }
    ~SynQueue() { }
    //将T类型对象放入队列
    void Put(const T &x)
    {
        unique_lock<mutex> lck(m_mutex);
        while(isFull())
        {
            m_notFull.wait(lck);
        }
        m_queue.push_back(x);
        //通过条件变量唤醒一个线程,也可以所有线程。 
        m_notEmpty.notify_one();
    }
    //将T类型对象从队列取出
    void Take(T &x)
    {
        unique_lock<mutex> lck(m_mutex);
        while(isEmpty())
        {
            std::cout << "no resource... please wait" << std::endl;
            m_notEmpty.wait(lck);
        }
        x = m_queue.front();
        m_queue.pop_front();
        m_notFull.notify_one();
    }
    //判断队列是否为空
    bool Empty()
    {
        unique_lock<mutex> lck(m_mutex);
        return m_queue.empty();
    }
    //判断队列是否为满
    bool Full()
    {
        unique_lock<mutex> lck(m_mutex);
        return m_queue.size() == m_maxsize;
    }
    //返回队列大小
    size_t Size()
    {
        unique_lock<mutex> lck(m_mutex);
        return m_queue.size();
    }

private:
    //判断空或满,内部使用不需要加锁。 
    inline bool isFull() const
    {
        return m_queue.size() == m_maxsize;
    }
    inline bool isEmpty() const
    {
        return m_queue.empty();
    }
    //队列
    list<T> m_queue;
    //互斥锁
    mutex m_mutex;
    //不为空时的条件变量
    condition_variable m_notEmpty;
    //不为满时的条件变量
    condition_variable m_notFull;
    //队列最大长度
    int m_maxsize;
};

void func(SynQueue<int> *sq)
{
    int ret;
    sq->Take(ret);
    cout << ret << endl;
}

int main()
{
    SynQueue<int> syn(20);
    for(int i = 0; i < 10; ++i)
    {
        syn.Put(i);
    }
    cout << syn.Size() << endl;
    vector<shared_ptr<thread>> tvec;
    for(int i = 0; i < 11; ++i)
    {
        //创建线程并且将管理线程的智能指针保存到容器中
        tvec.push_back(make_shared<thread>(func, &syn));
        //变为后台线程
        tvec[i]->detach();
    }
    std::this_thread::sleep_for(std::chrono::seconds(10));
    //添加一个资源
    syn.Put(11);
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 0;
} 

猜你喜欢

转载自blog.csdn.net/daaikuaichuan/article/details/81173493