C++多线程——访问异步操作结果(future)和条件变量(condition_variable)

一、std::future

  通常一个异步操作我们是不能马上就获取操作结果的,只能在未来某个时候获取。我们可以以同步等待的方式来获取结果,可以通过查询future的状态(future_status)来获取异步操作的结果。future_status有三种状态:

        1. deferred:异步操作还没开始;

        2. ready:异步操作已经完成;

        3. timeout:异步操作超时。

  获取future结果有三种方式:get、wait、wait_for,其中get等待异步操作结束并返回结果,wait只是等待异步操作完成,没有返回值,wait_for是超时等待返回结果。

二、std::promise

  promise 对象可以保存某一类型 T 的值,该值可被 future 对象读取(可能在另外一个线程中),因此 promise 也提供了一种线程同步的手段。在 promise 对象构造时可以和一个共享状态(通常是std::future)相关联,并可以在相关联的共享状态(std::future)上保存一个类型为 T 的值。可以通过 get_future 来获取与该 promise 对象相关联的 future 对象,调用该函数之后,两个对象共享相同的共享状态(shared state)

1. promise 对象是异步 Provider,它可以在某一时刻设置共享状态的值;

2. future 对象可以异步返回共享状态的值,或者在必要的情况下阻塞调用者并等待共享状态标志变为 ready,然后才能获取共享状态的值。

三、代码

#include <iostream>       // std::cout
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future

//获取future结果有三种方式:get、wait、wait_for,其中get等待异步操作结束并返回结果,
//wait只是等待异步操作完成,没有返回值,wait_for是超时等待返回结果。

void print_int(std::future<int>& fut) 
{
    int x = fut.get(); // 获取共享状态的值。 
    std::cout << "value: " << x << '\n'; // 打印 value: 10。 
}

int main ()
{
    std::promise<int> prom; // 生成一个 std::promise<int> 对象。 
    std::future<int> fut = prom.get_future(); // 和 future 关联。 
    std::thread t(print_int, std::ref(fut)); // 将 future 交给另外一个线程t。 
    prom.set_value(10); // 设置共享状态的值, 此处和线程t保持同步。 
    t.join();
    return 0;
}

四、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;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值