同步并发操作

本文主要说明条件变量的一些知识。

I、等待一个时间或其他条件

使用C++标准库提供的工具去等待事件的发生。通过另一个线程触发等待事件的机制是最基本的唤醒方式,这种机制就是条件变量

1.1 等待条件达成

C++标准库对条件变量有两套实现:std::condition_variablestd::condition_variable_any。两者都是需要与一个互斥量才能工作(互斥量是为了同步)。

下面代码说明了当有数据需要处理时,唤醒休眠中的线程对其进行处理的方式:

#include<iostream>
#include<mutex>
#include<condition_variable>
#include<queue>
#include<thread>

using namespace std;

std::mutex mut;
std::queue<data_chunk> data_queue;  //数据队列
std::condition_variable data_cond;

void data_preparation_thread() {
    while (more_data_to_prepare()) {
        data_chunk const data = prepare_data();
        std::lock_guard<std::mutex> lk(mut);    //对队列上锁,然后添加数据
        data_queue.push(data);  //
        data_cond.notify_one(); //通知等待线程
    }
}

//data处理线程
void data_processing_thread() {
    while (true) {
        std::unique_lock<std::mutex> lk(mut);   //对互斥量上锁,保护条件变量

        /* wait传入一个互斥量和检查条件
         * wait检查这些条件,如果条件不满足(为false)
         * wait将解锁互斥量,并将这个线程置于阻塞或等待状态。
         * 当准备数据的线程调用notify通知条件变量时,处理数据的线程从休眠中苏醒,重新获取互斥锁。
         * 并对条件再次检查,在条件满足的情况下,从wait返回并继续持有锁。当条件不满足时,线程对互斥量解锁,并重新开始等待。

         * 如果互斥量在线程休眠期间保持锁住状态,准备数据的线程将无法锁住互斥量,也就无法添加新的数据到队列中。

        */
        data_cond.wait(lk, []{return !data_queue.empty();});    //等待条件
        data_chunk data = data.front();
        data_queue.pop();
        lk.unlock();    //
        process(data);

        if (is_last_chunk(data))
            break;
    }
}

II、使用期望等待一次性事件

C++标准库模型将一次性事件成为期望(future)。当一个线程需要等待一个特定的一次性事件时,在某种程度上来说它就需要知道这个事件在未来的表现形式。之后,这个下线程会周期性的等待或检查事件是否触发;在检查期间也会执行其他任务。另外,在等待任务期间它可以先执行另外一些任务,知道对应的任务触发,而后等待期望的状态变为就绪(ready)。当时间发生时,这个期望就不会被重置。

C++标准库中,使用两种模板实现了两种期望:唯一期望std::future<>和共享期望std::shared_future<>

期望对象本身并不提供同步访问。当多个线程需要访问同一个独立期望对象时,它们必须使用互斥量或类似的同步机制对访问进行保护。

2.1 带返回值的后台任务

假设有一个需要长时间计算的任务,但是现在并不迫切需要这个计算结果。可以启动一个新线程来执行这个计算,但是这就意味着必须关注如何传回计算的结果,因为std::thread并不提供直接接收返回值的机制。这里需要std::async函数模板。当任务的结果并不着急要时,可以使用std::async启动一个异步任务。与std::thread对象等待方式不同,std::async会返回一个std::future对象,这个对象就是最终计算出来的结果。当需要这个值时,只需要调用这个对象的get()成员函数:

#include<future>
#include<iostream>

using namespace std;

int find_the_answer_to_ltuar();
void do_other_stuff();

int main() {

    //也可以想async中传递参数
    std::future<int> the_answer = std::async(find_the_answer_to_ltuae);
    do_other_stuff();
    cout << the_answer.get() << endl;

    return 0;
}

【参考】
[1] 《C++ Concurrency In Action》

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容