为了解释std::future
的用途,《并发编程》一书用了现实的例子作类比:假设我们要坐飞机去国外度假,在到达机场并完成了各种手续后,我们还要等待机场登机的通知,可能需要数个小时。在此期间,我们可以看书、上网或者喝咖啡,同时等待登机信号。标准库使用std::future
为类似的事件建模,如果线程需要等待特定的一次性事件,它就会获取一个future
代表这一事件,然后该线程可以周期性地在这个future
上等待一小段时间来检查事件是否发生(检查告示牌),而在检查间隙执行其他任务(喝咖啡)。如果它执行了其他任务,知道其所需的事件已发生才会继续进行原任务,随后等待future
就绪。一旦事件发生,即future
就绪,它就无法复位。
标准库提供两种future
,唯一的std::future<>
和可以被共享的std::shared_future<>
,分别根据std::unique_ptr
和std::shared_ptr
实现。前者的实例对应的关联事件仅被该实例拥有,后者的则能被多个std::shared_future
共享。虽然std::future
被应用于线程间通信,但它本身不提供同步访问,如果多个线程需要访问同一个future
对象,仍然需要互斥元或同步机制保护访问。
从后台任务中返回值
假设我们有一个长期运行计算的任务,计算最终会得到一个有用的结果。我们可以启动一个新的线程来执行计算,但必须将结果传回来。std::thread
没有提供这样的机制,我们需要借助std::async
。在不需要立刻得到结果的说以后,可以使用std::async
启动一个异步任务。std::async
返回一个std::future
对象,而不是一个std::thread
对象让你在上面等待,std::future
对象将持有计算的返回值。当需要这个值时,只要在future
上调用get()
,线程就会阻塞直到future
就绪,然后返回该值。看下面的实例:
#include <iostream>
#include <future>
int find_the_answer_to_ltuae();
void do_other_stuff();
int main()
{
std::future<int> the_answer = std::async(find_the_answer_to_ltuae);
do_other_stuff();
std::cout << "The answer is " << the_answer.get() << std::endl;
}
std::async
的使用和std::future
类似,如果第一个参数是指向成员函数的指针,第二个参数则提供了该函数的独享实例,其余的参数则是该函数的参数。如果第一个参数不是成员函数,第二个参数及以后则是该函数的参数。如果参数是右值,则通过移动原来的参数创造副本。看下面的代码:
#include <string>
#include <future>
struct X
{
void foo(int, std::string const&);
std::string bar(std::string const&);
};
X x;
auto f1 = std::async(&X::foo, &x, 42, "hello"); //调用的是x.foo()
auto f2 = std::async(&X::bar, x, "goodbye"); //调用的是tmpx.bar()
struct Y
{
double operator()(double);
};
Y y;
auto f3 = std::async(Y(), 3.141); //调用的是tmpy(3.141)
auto f4 = std::async(std::ref(y), 2.718); //调用的是y(2.718)
X baz(X&);
std::async(baz, std::ref(x));
class move_only
{
public:
move_only();
move_only(move_only&&);
move_only(move_only const&) = delete;
move_only& operator=(move_only&&);
move_only& operator=(move_only const&) = delete;
void operator()();
};
auto f5 = std::async(move_only());
std::async
是否启动一个新线程,或者在等待future
时是否同步运行都取决于具体实现方式,但我们可以在调用时指定额外的参数来调整。这个参数可以是std::launch
类型,如std::launch::defered
,表明函数调用将会延迟,直到在future
调用wait()
或get()
位置;也可以是std::launch::async
,表示该函数必须运行在自己的线程上;也可以是std::launch::defered | std::launch::async
,表示由具体实现决定。如果是第一种情况,即延迟调用,函数实际上可能永远不会运行,例如:
auto f6 = std::async(std::launch::async, Y(), 1.2); //在新线程中运行
auto f7 = std::async(std::launch::defered, baz, std::ref(x)); //在wait()或get()中运行
auto f8 = std::async(std::launch::defered | std::launch::async, baz, std::ref(x)); //由具体实现决定
auto f9 = std::async(baz, std::ref(x)); //由具体实现决定
f7.wait(); //延迟调用
将任务与future相关联
std::packaged_task<>
将一个future绑定到一个可调用对象上。当std::packaged_task<>
对象被调用时,它就调用关联的可调用对象,让future
就绪,将返回值作为关联数储存。std::packaged_task<>
类模板的参数为函数签名,比如void()
表示无参数无返回值的函数,或是int(std::string&, double*)
表示接收对std::string
的非const引用和指向double的只恨,并返回int的函数。返回类型确定了从get_future()
成员函数返回的std::future<>
的类型,函数签名的参数列表则指定了operator()
的参数类型。例如,一个特化的std::packaged_task<std::string(std::vector<char>*, int)>
的部分定义如下:
template<>
class packaged_task<std::string(std::vector<char>*, int)>
{
public:
template<typename Callble>
explicit packaged_task(Callble&& f);
std::future<std::string> get_future();
void operator()(std::vector<char>*, int);
};
它是一个可调用对象,可以被封装入一个std::function
,作为线程函数传给std::thread
,或者传给需要可调用对象的另一个函数,或者被直接调用。当std::packaged_task
作为函数对象被调用时,提供给operator()
的参数被传给所包含的函数,并且将返回值作为异步结果,存放在get_future()
获取的std::future
中。因此可以将任务封装在std::packaged_task
中,并且从中获取future
,在需要结果时等待future
就绪。看下面的例子:
#include <deque>
#include <mutex>
#include <future>
#include <thread>
#include <utility>
std::mutex m;
std::deque<std::packaged_task<void()>> tasks;
bool gui_shutdown_message_received();
void get_and_process_gui_message();
void gui_thread()
{
while(!gui_shutdown_message_received())
{
get_and_process_gui_message();
std::packaged_task<void()> task;
{
std::lock_gurad<std::mutex> lk(m);
if(tasks.empty())
continue;
task = std::move(tasks.front()); // 从队列中提取任务
tasks.pop_front();
}
task(); // 释放锁,运行任务。完成后future就绪
}
}
std::thread gui_bg_thread(gui_thread);
templace<typename Func>
std::future<void> post_task_for_gui_thread(Func f)
{
std::packaged_task<void()> task(f); // 创建新任务
std::future<void> res = task.get_future(); // 获取future
std::lock_guard<std::mutex> lk(m);
tasks.push_back(std::move(task)); // 将任务置于队列
return res; // 返回future
}
0 条评论