一、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;
}