文章目录
线程协作的更高层次的抽象
std::thread
库对OS 线程系列API的封装,可以非常方便地使用线程,互斥量,条件变量。但是直接使用std::thread
库中的互斥量,条件变量来实现线程的同步或协作,还是属于底层的抽象。
比如两个线程thread1,thread2间的协作,两个线程间共享一个变量,thread2需要等待thread1将变量设置成某一个值,才继续运行。此时需要用到互斥量,条件变量。其实,在实现思路是比较简单的,但是实现上还是比较复杂。
std::tread
库提供了更高的抽象封装
std::future
,std::promise
,std::package_task
。通过std::future
和std::promise
可以很简单的实现上面的例子。
std::future
它提供了一种机制可以获取异步操作的结果,因为一个异步操作的结果不能马上获取,只能在未来某个时候从某个地方获取。这个异步操作的结果是一个未来的期待值,所以被称为future
,未来量。
-
std::future
通常结合std::promise
,std::package_task
,std::async
使用 -
std::future
是一个类模版,模版参数需要传入一个类型
std::promise
称之为承诺量,它结合std::future
一起使用(给予承诺,在未来某个时刻获取)。与futrue
结合时相当与建立一个线程间的通道:一边往promise里放东西(set_value),一边取去东西(通过futrue的get方法)。
std::promise
也是一个类模版,模版参数需要传入一个类型。每个std::promise
对象都关联了一个std::future
对象
用法
建立”通道“
使用std::promise
与std::future
给线程间建立一个”通道“,分为以下几步:
- 在线程1中创建一个
std::promise
对象
std::promise<int> promiseObj
-
将
promiseObj
传给线程2 -
线程1使用
promiseObj
的get_future()
方法获得一个std::future
对象
std::future<int> futureObj = promiseObj.get_future()
此时”通道“就已经建立,就是通过std::promise
的get_future()
方法获取std::future
对象。
使用“通道”
接上面的步骤
-
在线程1中调用
futureObj
的get()
方法,如果promiseObj
没有设置值,则该方法会一直阻塞 -
在线程2中给
promiseObj
设置一个值
promiseObj.set_value(45)
协作流程图
代码示例
#include <thread>
#include <future>
#include <iostream>
void threadFunc(std::promise<int> promObj)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
promObj.set_value(18);
}
int main()
{
std::promise<int> promiseObj;
std::future<int> futureObj = promiseObj.get_future();
//创建线程2并将promiseObj传给它
std::thread th(threadFunc,std::move(promiseObj));
std::cout<<futureObj.get()<<std::endl;
th.join();
}
注意:std::future
和std::promise
都不支持复制语意,只支持移动语意
相比通过std::condition_variable
与std::mutex
的方式,这种实现方式显然更简单。
std::packaged_task
std::packaged_task
它是个函数对象,可以像正常函数一样被执行。它封装一个可调用对象,传递给一个std::thread
对象,在线程中执行。通过std::future
来获得执行结果。
int PrintFunc(int v)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<"v "<<v<<std::endl;
return v;
}
上面的一个简单的函数,可以通过 std::package_task
对它进行封装,来实现异步调用。
//包装PrintFunc
std::packaged_task<int (int)> task(PrintFunc);
//获取future
std::future<int> f = task.get_future();
//创建线程异步调用
std::thread thread2(std::move(task),18);
完整的代码如下:
#include <thread>
#include <future>
#include <iostream>
int PrintFunc(int v)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<"v "<<v<<std::endl;
return v;
}
int main()
{
std::packaged_task<int(int)> task(PrintFunc);
std::future<int> f = task.get_future();
std::thread thread2(std::move(task),18);
std::cout<<f.get()<<std::endl;
thread2.join();
}
当然也可以通过std::promise
结合std::future
来实现,但是需要修改函数的声明,要把std::promise
对象传入,与std::packeaged_task
的实现,相当于是一种侵入式的实现。
std::packaged_task
对可调用对象进行封装,包括普通函数,lambda,函数对象。比如通过lambda来创建std::package_task
对象
std::packaged_task<int(int)> task([](int v){
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout<<"v "<<v<<std::endl;
return v;
});
std::future与std::shared_future
-
std::funture
只支持移动语义,它要求类型参数也支持移动语义。它的get
方法只能调用一次,调用get
相当于将结果移走(移动语义的通俗意义)。再次调用会抛异常。 -
std::shared_future
可以共享结果,它要求类型参数支持复制语义
#include <future>
#include <iostream>
int Getvalue()
{
return 18;
}
int main()
{
std::packaged_task<int()> task(Getvalue);
//std::future<int> r = task.get_future();
std::shared_future<int> r = task.get_future();
std::thread t1(std::move(task));
std::cout << r.get() << std::endl;
std::cout << r.get() << std::endl;
t1.join();
}
std::future 与 std::promise
std::future
与std::promise
都有一个对void
的特化版本。这种情况下,两个线程间不是传递参数,而是进行同步:当一个线程在一个future<void>
上等待时(是使用get()
),另外一个线程可以通过promise<void>
上的set_value()
让其结束等待,继续往下执行。
#include <iostream>
#include <future>
#include <chrono>
void thread1(std::promise<void> v)
{
std::this_thread::sleep_for(std::chrono::seconds(1));
v.set_value();
std::cout << "======> end" << std::endl;
}
int main()
{
std::promise<void> v1;
std::future<void> r = v1.get_future();
std::thread t(thread1,std::move(v1));
r.get();
t.join();
}
还有更高层次的抽象std::async
对比std::promise
,packaged_task
。std::async
的抽象层次更高,它代表了异步操作,不需要手动启动线程。在传入std::async
的执行策略为std::launch::async
时,将自动产生一个线程。异步执行的结果通过std::future
获取。
std::async
跟std::thread
类似,传入一个执行体及它对应的参数。执行策略作为可选参数std::async
方法返回的是一个std::future
对象,用来获取结果
#include <chrono>
#include <future>
#include <iostream>
#include <thread>
int func()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << std::this_thread::get_id() << std::endl;
return 18;
}
int main()
{
std::cout << "main thread id " << std::this_thread::get_id() << std::endl;
//std::async(func)
//std::launch::deferred
auto f = std::async(std::launch::async, func);
std::cout<< f.get()<<std::endl;
}
上面的例子中,std::launch::async
就是异步调用,会产生一个新线程,在其中执行。std::launch::deferred
不会产生新线程,在返回的std::future
对象调用get()
方法时,被执行。默认是策略是std::launch::async
小结
C++ thread库相比 java 的thread库,功能还是比较薄弱的,比如没有线程池。抽象程度也比较弱。但是聊胜于无,它首先是跨平台的,属于STL,可以不再引入第三方库。尽管功能及抽象层次不高,但是也不用再与OS的线程API打交道,陷入使用不同OS API的细节。