我们前面介绍的std::thread
是C++11中提供异步创建多线程的工具,只能是异步运行任务,却无法获取任务执行的结果,一般都是依靠全局对象,全局对象在多线程下是及其不安全的,为此标准库提供了std::future
类模板来关联线程运行的函数和函数的返回结果,这种获取结果的方式是异步的。
std::future 通常由某个 Provider 创建,你可以把 Provider 想象成一个异步任务的提供者,Provider 在某个线程中设置共享状态的值,与该共享状态相关联的 std::future 对象调用 get(通常在另外一个线程中) 获取该值,如果共享状态的标志不为 ready,则调用 std::future::get 会阻塞当前的调用者,直到 Provider 设置了共享状态的值(此时共享状态的标志变为 ready),std::future::get 返回异步任务的值或异常(如果发生了异常)。
一个有效(valid)的 std::future 对象通常由以下三种 Provider 创建,并和某个共享状态相关联。Provider 可以是函数或者类,他们分别是:
一个 std::future 对象只有在有效(valid)的情况下才有用(useful),由 std::future 默认构造函数创建的 future 对象不是有效的(除非当前非有效的 future 对象被 move 赋值另一个有效的 future 对象)。
template
class future
{
public:
future() noexcept;
future(future&&) noexcept;
future& operator=(future&&) noexcept;
~future();
future(future const&) = delete;
future& operator=(future const&) = delete;
bool valid() const noexcept;
ResultType get();
shared_future share();
void wait();
template
future_status wait_for(
std::chrono::duration const& relative_time);
template
future_status wait_until(
std::chrono::time_point const& absolute_time);
};
构造函数:
std::future fut; // 默认构造函数
fut = std::async(do_some_task); // move-赋值操作。
析构函数:
get函数:
std::future::get()
(void特例化)不返回任何值,但仍等待共享状态就绪并释放它。#include // std::cout
#include // std::async, std::future
#include // std::move
int do_get_value() { return 11; }
int main () {
// 由默认构造函数创建的 std::future 对象,
// 初始化时该 std::future 对象处于为 invalid 状态.
std::future foo, bar;
foo = std::async(do_get_value); // move 赋值, foo 变为 valid.
bar = std::move(foo); // move 赋值, bar 变为 valid, 而 move 赋值以后 foo 变为 invalid.
if (foo.valid()) {
std::cout << "foo's value: " << foo.get() << '\n';
} else {
std::cout << "foo is not valid\n";
}
if (bar.valid()) {
std::cout << "bar's value: " << bar.get() << '\n';
} else {
std::cout << "bar is not valid\n";
}
return 0;
}
operator=:
share函数:
std::shared_future
对象,该对象获取future对象的共享状态。调用该函数之后,该 std::future
对象本身已经不和任何共享状态相关联,因此该std::future
的状态不再是 valid 的了。valid函数:
future
对象是否与共享状态关联。一个有效的std::future
对象只能通过 std::async()
, std::future::get_future
或者 std::packaged_task::get_future
来初始化。另外由 std::future 默认构造函数创建的 std::future 对象是无效(invalid)的,当然通过 std::future 的 move 赋值后该 std::future 对象也可以变为 valid。一旦调用了std::future::get()
函数,再调用此函数将返回false。wait函数:
wait()
并不读取共享状态的值或者异常。wait_for函数:
future_status
。此枚举类有三种label:
wait_until函数:
future_status
。下面来看看详细的代码:
#include
#include
#include
#include
#include
int test_future_1()
{
{ // constructor/get/operator=
auto get_value = []() { return 10; };
std::future foo; // default-constructed
// move-constructed
std::future bar = std::async(get_value);
int x = bar.get();
std::cout << "value: " << x << '\n'; // 10
std::future foo2(std::async(get_value));
std::cout << "value: " << foo2.get() << '\n'; // 10
}
{ // share
std::future fut = std::async([]() { return 10; });
std::shared_future shfut = fut.share();
//std::cout << "value: " << fut.get() << '\n';
// crash, 执行完fut.share()后,fut对象将变得无效
std::cout << "fut valid: " << fut.valid() << '\n';// 0
// shared futures can be accessed multiple times:
std::cout << "value: " << shfut.get() << '\n'; // 10
// 20, 对于std::shared_future对象,get函数可以被多次访问
std::cout << "its double: " << shfut.get() * 2 << '\n';
}
{ // valid
std::future foo, bar;
foo = std::async([]() { return 10; });
bar = std::move(foo);
if (foo.valid()) {std::cout << "foo's value: " << foo.get() << '\n';}
else {std::cout << "foo is not valid\n";}
if (bar.valid()) {std::cout << "bar's value: " << bar.get() << '\n';}
else {std::cout << "bar is not valid\n";}
}
{ // wait
auto is_prime = [](int x) {
for (int i = 2; i < x; ++i) if (x%i == 0) return false;
return true;
};
// call function asynchronously:
std::future fut = std::async(is_prime, 194232491);
std::cout << "checking...\n";
fut.wait();
std::cout << "\n194232491 ";
// guaranteed to be ready (and not block) after wait returns
if (fut.get()) {
std::cout << "is prime.\n";
} else {
std::cout << "is not prime.\n";
}
}
{ // wait_for
auto is_prime = [](int x) {
for (int i = 2; i < x; ++i) if (x%i == 0) return false;
return true;
};
// call function asynchronously:
std::future fut = std::async(is_prime, 700020007);
// do something while waiting for function to set future:
std::cout << "checking, please wait";
std::chrono::milliseconds span(100);
// 可能多次调用std::future::wait_for函数
while (fut.wait_for(span) == std::future_status::timeout)
std::cout << '.';
bool x = fut.get(); // retrieve return value
std::cout << "\n700020007 " << (x ? "is" : "is not") << " prime.\n";
}
return 0;
}
int test_future_2()
{
// future from a packaged_task
std::packaged_task task([] { return 7; }); // wrap the function
std::future f1 = task.get_future(); // get a future
std::thread t(std::move(task)); // launch on a thread
// future from an async()
std::future f2 = std::async(std::launch::async, [] { return 8; });
std::cout << "Waiting..." << std::flush;
f1.wait();
f2.wait();
std::cout << "Done!\nResults are: " << f1.get()
<< ' ' << f2.get() << ' ' << '\n';
t.join();
return 0;
}
void initiazer(std::promise * promObj)
{
std::cout << "Inside Thread" << std::endl;
promObj->set_value(35);
}
int test_future_3()
{
std::promise promiseObj;
std::future futureObj = promiseObj.get_future();
std::thread th(initiazer, &promiseObj);
std::cout << "value: " << futureObj.get() << std::endl;
th.join();
// If std::promise object is destroyed before setting the value
// the calling get() function on associated std::future object will throw exception.
// A part from this, if you want your thread to return multiple values
// at different point of time then
// just pass multiple std::promise objects in thread
// and fetch multiple return values from thier associated multiple std::future objects.
return 0;
}
int main() {
test_future_1();
test_future_2();
test_future_3();
}
通过std::async()
创建异步任务的std::future
,std::async
的创建任务的传参方式和 std::thread 一样,也可以使用类的成员方法进行创建:
#include
#include
class A {
public:
int f(int i) { return i; }
};
int main() {
A a;
std::future res = std::async(&A::f, &a, 1);
std::cout << res.get(); // 1,阻塞至线程返回结果
}
需要注意,std::future
只能 get() 一次,多次get会抛出异常:
#include
#include
int main() {
std::future res = std::async([] {});
res.get();
try {
res.get();
} catch (const std::future_error& e) {
// exception: std::future_error: No associated state
std::cout << e.what() << std::endl;
}
}
std::shared_future
类型模板是为了等待其他线程上的异步结果。其和std::promise
,std::packaged_task
类型模板,还有std::async
函数模板,都是为异步结果准备的工具。与std::future
唯一的区别就是多个std::shared_future
实例可以引用同一个异步结果。
std::shared_future
实例是CopyConstructible
(拷贝构造)和CopyAssignable
(拷贝赋值)。你也可以用ResultType
的std::future
类型对象,移动构造一个std::shared_future
类型对象。
访问给定std::shared_future
实例是非同步的。因此,当有多个线程访问同一个std::shared_future
实例,且无任何外围同步操作时,这样的访问是不安全的。不过访问关联状态时是同步的,所以多个线程访问多个独立的std::shared_future
实例,且没有外围同步操作的时候,是安全的。
shared_future的接口与future基本一致,这里就不再详细介绍了:
template
class shared_future
{
public:
shared_future() noexcept;
shared_future(future&&) noexcept;
shared_future(shared_future&&) noexcept;
shared_future(shared_future const&);
shared_future& operator=(shared_future const&);
shared_future& operator=(shared_future&&) noexcept;
~shared_future();
bool valid() const noexcept;
ResultType get() const;
void wait() const;
template
future_status wait_for(
std::chrono::duration const& relative_time) const;
template
future_status wait_until(
std::chrono::time_point const& absolute_time)
const;
};
std::shared_future
可以多次获取结果,它可以通过std::future
的右值构造。每一个std::shared_future
对象上返回的结果不同步,多线程访问std::shared_future
需要加锁防止 race condition,更好的方法是给每个线程拷贝一个 std::shared_future
对象,这样就可以安全访问而无需加锁:
#include
#include
int main() {
std::promise ps;
std::future ft = ps.get_future();
std::shared_future sf(std::move(ft));
// 或直接 std::shared_future sf{ps.get_future()};
ps.set_value(1);
int ret = sf.get();
std::cout << "get1: "<< ret << std::endl;
ret = sf.get(); //std::shared_future可以多次get
std::cout << "get2: "<< ret << std::endl;
}
输出:
get1: 1
get2: 1
也可以直接用std::future::share()
生成std::shared_future
:
#include
#include
int main() {
std::promise ps;
auto sf = ps.get_future().share();
ps.set_value(2);
int ret = sf.get();
std::cout << "get1: "<< ret << std::endl;
ret = sf.get();
std::cout << "get2: "<< ret << std::endl;
}
输出:
get1: 2
get2: 2
其实写这个有点超出本人目前的能力范围了,因为还没能力去读它的源码。但是还是想在这里猜想一下,从当前的这个工作场景来其实很像很像我们之前介绍过的条件变量:C++多线程:condition_variable - 掘金 (juejin.cn)。我们不防猜想一下:
情况1:可以使用条件变量实现,future对象中设置一个条件变量,在异步线程结束时调用notify_one(),在get()函数中调用wait(),这样可以实现一个简单的异步调用,缺点是需要互斥量,条件变量,一个仅发生一次的过程这样不免有些浪费,wait()操作更是需要加锁解锁,也就是说这样一个完整的过程我们需要加锁解锁各两次,还需要一个notify_one(),但优点也很明显,过程简单,且如果等待时间较长,可以把cpu让给其他工作线程,全局上节省的时间随等待时间加长而变长,但等待时间短的话除了完成功能就没有丝毫优势了。
情况2:也可以使用自旋锁实现,自旋锁(spinlock),是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。future对象只需要设置一个类内的原子变量,当异步线程结束后改变值,然后get()成员自旋等待即可,这种方法优点与缺点都是很明显的,优点:比起条件变量这样笨重的东西确实轻盈了不少,且在等待时间较小时不存在条件变量所需要的用户态与内核态之间的转换;缺点:获取锁的线程一直处于活跃状态,但是并没有执行任何有效的任务,因此使用这种锁会造成busy-waiting,也就是CPU不断的进行检测操作,而无法处理其他任务。当等待时间较长的时候cpu空转,无意义的消耗,当然这是自旋锁本身的弊端。
最后查阅了一些资料证实了future最终是使用了情况2-自旋锁实现,但是与标准的自旋锁有一些差异,具体的细节以后有能力了再详细介绍了。
参考
C++11 并发指南四( 详解三 std::future & std::shared_future) - Haippy - 博客园 (cnblogs.com)