[5 使用C++11让多线程开发变得简单] 5.3 条件变量

条件变量能阻塞线程(wait),直到收到另一个线程发出的通知或超时,才会唤醒当前阻塞的线程。

C++11提供了两种条件变量:

(1)condition_variable,wait函数需要与std::unique_lock<std::mutex>配合使用

(2)conditon_vatiable_any,可和任意mutex配合使用,更灵活,但效率较低

条件变量的使用过程:

(1)拥有条件变量的线程获取互斥量

(2)循环检查某个条件,如果条件不满足,则阻塞直到条件满足;如果条件满足,则向下执行。

(3)某线程满足条件后,调用notify_one或notify_all唤醒一个或所有的等待线程。

可用条件变量来实现一个同步队列,同步队列作为一个线程安全的数据共享区,用于线程之间读取数据。

有限长度同步队列的实现(condition_variable_any):

#include <mutex>
#include <thread>
#include <condition_variable>

template<typename T>
class SyncQueue
{
    bool IsFull() const
    {
        return m_queue.size() == m_maxSize;
    }
    bool IsEmpty() const
    {
        return m_queue.empty();
    }
public:
    SyncQueue(int maxSize):m_maxSize(maxSize)
    {
    }    

    void Put(const T& x)
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        while (IsFull())
        {
            cout << "缓冲区满了,需要等待..." << endl;
            m_notFull.wait(m_mutex);
        }
        
        m_queue.push_back(x);
        m_notEmpty.notifyone();
    }

    void Take(T& x)
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        while (IsEmpty())
        {
            cout << "缓冲区空了,需要等待..." << endl;
            m_notEmpty.wait(m_mutex);
        }
        x = m_queue.front();
        m_queue.pop_front();
        m_notFull.notify_one();
    }

    // below three func,just for public func
    bool Empty()
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        return m_queue.empty();
    }

    bool Full()
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        return m_queue.size() == m_maxSize;
    }

    size_t Size()
    {
        std::lock_guard<std::mutex> locker(m_mutex);
        return m_queue.size();
    }

private:
    std::list<T> m_queue;
    std::mutex m_mutex;
    // 队列不为空的条件变量
    std::condition_variable_any m_notEmpty;
    // 队列没有满的条件变量
    std::condition_variable_any m_notFull;
    // 同步队列最大长度
    int m_maxSize;
};

这个同步队列在没有满的时候可以插入数据,如果队列满了,生产线程会在m_notFull.wait阻塞等待(wait中释放锁),等消费线程取出数据后发送notify_one通知,则生产线程被唤醒继续执行;如果队列空了,消费线程会在m_notEmpty.wati阻塞等待(wait中释放锁),等生产线程插入数据后发送notify_one通知,则消费线程被唤醒继续执行。

条件变量的wait函数还有一个重载方法,可以接受一个条件:

std::lock_guard<std::mutex> locker(m_mutex);
while (IsFull())
{
    m_notFull.wait(m_mutex);
}
可以改写为:
std::lock_guard<std::mutex> locker(m_mutex);
m_notFull.wait(locker, [this]{return !IsFull();});

解释下Take中锁的状态:

首先lock_guard初始化时获取锁。如果队列满了,生产线程的m_notFull.wait会释放锁,然后阻塞。等消费线程发送了notify_one通知后,生产线程被唤醒后会重新获取锁,继续执行。直到Take函数执行完成,locker生命周期结束,再次释放锁。

std::unique_lock和std::lock_guard的差别在于前者可以自由地释放mutex,后者需要等到std::lock_guard变量生命周期结束时才能释放。

无限长度的同步队列(condition_variable,unique_lock):

#include <thread>
#include <condition_variable>
#include <mutex>
#include <list>
#include <iostream>
using namespace std;

template<typename T>
class SimpleSyncQueue
{
    public:
        SimpleSyncQueue()
        {
        }

        void Put(const T& x)
        {
            std::lock_guard<std::mutex> locker(m_mutex);
            m_queue.push_back(x);
            m_notEmpty.notify_one();
        }
        
        void Take(T& x)
        {
            std::unique_lock<std::mutex> locker(m_mutex);
            // 如果条件不满足(return false),即队列为空,继续等待wait
            m_notEmpty.wait(locker, [this]{return !m_queue.empty();});
            x = m_queue.front();
            m_queue.pop_front();
        }        

        // below 2 funcs just public funcs
        bool Empty()
        {
            std::lock_guard<std::mutex> locker(m_mutex);
            return m_queue.empty();
        }

        size_t Size()
        {
            std::lock_guard<std::mutex> locker(m_mutex);
            return m_queue.size();
        }

    private:
        std::list<T> m_queue;
        std::mutex m_mutex;
        // 队列不空的条件变量
        std::condition_variable m_notEmpty;
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值