c++ 多线程学习-thread库(3.1)单线程等待一个事件或者某个条件

当一个线程等待另一个线程完成任务时,有几种选择。
第一种,线程持续性的检查共享数据标志。但是这样会造成性能的浪费,而且重复性的访问势必会对数据进行长时间的上锁,从而造成其他线程无法访问,这样线程就会持续等待。
第二种,比第一种稍微好点,在线程等待时间内使用std::this_thread::sleep_for 进行周期性的间歇。但是在某些环境下会造成一些遗漏,例如一些高节奏游戏中很可能会造成丢帧,或者在一个实时应用中超越一个时间片。
第三种,使用c++多线程标准库中的“条件变量”去实现,即一个线程完成事会触发另外一个线程中的变量觉醒从而唤醒整个线程。

std::condition_varialestd::condition_variable_any都包含于<condition_variable>头文件的声明中。std::condition_variale需要搭配std::mutex使用,系统资源开销较小,对硬件要求低,作为首选。std::condition_variable_any可以搭配任何互斥量,使用比较灵活,但是系统资源的开销大,对硬件要求较高,作为第二选择。

例1 使用std::condition_variale处理数据等待

std::mutex mut;
std::queue<data_chunk> data_queue;  //1
std::condition_variable data_cond;

void data_prparation_thread()
{
    while(more_data_to_prepare())
    {
    data_chunk const data = preapare_data();
    std::lock_guard<std::mutex> lk(mut);
    data_queue.push(data);  //2
    data_cond.notify_one();  //3
    }
}

void data_processing_thread()
{
    while(true)
    {
    std::unique_lock<std::mutex> lk(mut);  //4
    data_cond.wait(lk, []{return !data_queue.empty();});  //5
    data_chunk data = data_queue.front();
    data_queue.pop();
    lk.unlock();  //6
    process(data);
    if(is_last_chunk(data))
        break;
    }
}

首先,声明一个用来在线程中传递数据的队列(如步骤1所示)。data_prparation_thread()函数中,当数据准备好的时候,对互斥量上锁用来锁定队列,并将准备好的数据推入队列中(如步骤2所示)。然后调用std::condition_variable::notify_one()函数,对等待线程进行通知(如步骤3所示)。
另外,在另一个线程中,data_processing_thread()函数首先对队列进行上锁,然后调用
std::condition_variable::wait()函数判断队列是否为空。如果队列为空,则解锁互斥量,释放队列,并使线程进入阻塞状态或睡眠状态等待其他线程中的std::condition_variable::notify_one()函数的唤醒。当该线程被唤醒时,将再次获取互斥锁并判断判断条件,若满足条件就从std::condition_variable::wait()函数中返回,进入后续程序。

例2. 构建线程安全队列

#include <queue>
#include <memory>
#include <mutex>
#include <condition_variable>

template<typename T>
class threadsafe_queue
{
private:
    mutable std::mutex mut;
    std::queue<T> data_queue;
    std::condition_variable data_cond;
public:
    threadsafe_queue()
    {}
    threadsafe_queue(threadsafe_queue const& other)//复制构造函数
    {
        std::lock_guard<std::mutex> lk(other.mut);
        data_queue = other.data_queue;
    }

    void push(T new_value)
    {
        std::lock_guard<std::mutex> lk(mut);
        data_queue.push(new_value);
        data_cond.notify_one();
    }

    void wait_and_pop(T& value)
    {
        std::unique_lock<std::mutex> lk(mut);
        data_cond.wait(lk, [this]{return !data_queue.empty();}) //等待push函数中data_cond.notify_one()函数的唤醒
        value = data_queue.front();
        data_queue.pop();
    }

    std::shared_ptr<T> wait_and_pop()
    {
        std::unique_lock<std::mutex> lk(mut);
        data_cond.wait(lk, [this]{return !data_queue.empty();});//等待唤醒
        std::shared_ptr<T> res(std::make_shared<T> (data_queue.front()));
        data_queue.pop();
        return res;
    }

    bool try_pop(T& value)
    {
        std::lock_guard<std::mutex> lk(mut);
        if(data_queue.empty())
            return false;
        value = data_queue.front();
        data_queue.pop();
        return true;
    }

    std::shared_ptr<T> try_pop()
    {
        std::lock_guard<std::mutex> lk(mut);
        if(data_queue.empty())
            return std::shared_ptr<T>();
        std::shared_ptr<T> res(std::make_shared<T>(data_queue.front()));
        data_queue.pop();
        return res;
    }

    bool empty() const
    {
        std::lock_guard<std::mutex> lk(mut);
        return data_queue.empty();
    }
};

在例2中有wait_and_pop()try_pop()两种弹出函数,try_pop()函数仅仅可以进行线程安全的弹出操作但是功能比较弱,无法进行等待。而wait_and_pop()则可以进行判断和等待。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值