Waiting for one-off events with futures
- std::shared_future和std::future 的行为和
std::shared_ptr和std::unique_ptr类似. - future一旦变成ready状态.就不能reset状态了.
- 这两个future都是模板.模板参数就是关联数据.
std::future<void>
表示没有关联数据.- 如果很多个线程需要访问同一个future你需要上锁来访问.但是
shared_future
就不需要加锁,多个线程都可以访问. - 还有一个命名空间中也有两个future.
std::experimental
这个空间的代码质量也很高,只是它如果被纳入std.代码形式会不一样.你需要警惕,代码未来的不一致行为和维护成本.
Returning values from background tasks
#include <future>
#include <iostream>
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;
//(sai:如何使用future对象,延迟获取结果)
}
std::async
#include <string>
#include <future>
struct X
{
void foo(int,std::string const&);
std::string bar(std::string const&);
};
X x;
//<--Calls p->foo(42,"hello") where p is &x 注意这里是引用x
auto f1=std::async(&X::foo,&x,42,"hello");
//<--Calls tmpx.bar("goodbye") where tmpx is a copy of x 注意这里是拷贝x
auto f2=std::async(&X::bar,x,"goodbye");
struct Y
{
double operator()(double);
};
Y y;
auto f3=std::async(Y(),3.141); //<--Calls tmpy(3.141) where tmpy is move-constructed from Y()
auto f4=std::async(std::ref(y),2.718); //<--Calls y(2.718)
X baz(X&);
std::async(baz,std::ref(x)); //<--Calls baz(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()); //<--Calls tmp() where tmp is constructed from std::move(move_only())
参数调用各种调用方法,
你可以制定是wait的时候延迟启动函数.还是即时启动线程.默认参数是让具体C++实现自己去选择.
auto f6=std::async(std::launch::async,Y(),1.2); //<--Run in new thread
auto f7=std::async(std::launch::deferred,baz,std::ref(x)); //<--Run in wait() or get()
auto f8=std::async( //<--Implementation chooses
std::launch::deferred | std::launch::async, //<--Implementation chooses
baz,std::ref(x)); //<--Implementation chooses
auto f9=std::async(baz,std::ref(x)); //<--Implementation chooses
f7.wait(); //<--Invoke deferred function
//(sai:如何按照各种需求启动异步线程)
//(sai:注意std::async是一个模板函数,而promise和packaged_task是模板类,都是为了实现异步线程)
Associating a task with a future
穿件task对象,里面带有一个future.我们不用手动写future返回值.这会更方便.这就是
packaged_task
, 它的函数原型为.
template<>
class packaged_task<std::string(std::vector<char>*,int)>
{
public:
template<typename Callable>
explicit packaged_task(Callable&& f);
std::future<std::string> get_future();
void operator()(std::vector<char>*,int);
};
packaged_task
是一个模板,参数是函数签名(函数类型).
PASSING TASKS BETWEEN THREADS
#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() //<--1
{
while(!gui_shutdown_message_received()) //<--2
{
get_and_process_gui_message(); //<--3
std::packaged_task<void()> task;
{
std::lock_guard<std::mutex> lk(m);
if(tasks.empty()) //<--4
continue;
task=std::move(tasks.front()); //<--5
tasks.pop_front();
}
task(); //调用task对象的call operator.
}
}
std::thread gui_bg_thread(gui_thread);
template<typename Func>
std::future<void> post_task_for_gui_thread(Func f)
{
std::packaged_task<void()> task(f); //<--7
std::future<void> res=task.get_future(); //<--8
std::lock_guard<std::mutex> lk(m);
tasks.push_back(std::move(task)); //<--9
return res; //<--10
}
GUI多线程处理方式,使用packaged_task和future
std::packaged_task<void()>是无参数无返回值函数的意思,即便有返回值函数,也会不忽略丢弃.
Making (std::)promises
- 由此可见package_task是跑一个函数线程并存储数据到它里面引用的future对象.
future对象是movable but not copyable的. - 你如何不启动
package_task
和 async 来创建future并设置数据返回.
你因为可能需要用一个线程来处理多个链接,然后来处理数据返回给用户.因为
package_task
,async
只能和一个future相关联. 这时候你需要用
promises
#include <future>
void process_connections(connection_set& connections)
{
while(!done(connections)) //<--1
{
for(connection_iterator connection=connections.begin(),end=connections.end(); connection!=end; ++connection)
{
if(connection->has_incoming_data()) //<--3
{
data_packet data=connection->incoming();
std::promise<payload_type>& p = connection->get_promise(data.id); //<--4
p.set_value(data.payload);
}
if(connection->has_outgoing_data()) //<--5
{
outgoing_packet data = connection->top_of_outgoing_queue();
connection->send(data.payload);
data.promise.set_value(true); //<--6
}
}
}
}
Saving an exception for the future
如果在线程中出现异常如何通过future传递异常.
double square_root(double x)
{
if(x<0)
{
throw std::out_of_range("x<0");
}
return sqrt(x);
}
如果下面这么调用就会发生异常
double y=square_root(-1);
你在获取future的时候会怎么样?
std::future<double> f=std::async(square_root,-1);
double y=f.get();
如果异步线程中抛出一个异常,那么使用future的get的时候会触发异常再次抛出。但是C++不保证再次抛出的异常是不是原异常本身还是拷贝。不同的编译器有不同的实现。同样的道理也包含
package_task
.另外promises也含有这样的机制.只是触发行为不一样.
std::promise 抛出异常需要手动条用一次set_exception()
extern std::promise<double> some_promise;
try
{
some_promise.set_value(calculate_value());
}
catch(...)
{
some_promise.set_exception(std::current_exception());
}
可以直接抛出异常,也可以直接拷贝一个异常并不在当时抛出。留给future的get时抛出异常.
some_promise.set_exception(std::copy_exception(std::logic_error("foo ")));
应该优先使用这种异常抛出方式,因为编译器可以进行优化
- 如果在set future之前摧毁promise或者packaged
task那么会触发异常。这是为了不让等待线程一直等待下去 - future只能允许一个线程等待,如果要允许多个线程等待。请使用shared_future
Waiting from multiple threads
- future get 之后其他进程访问就拿不到数据了。会引发异常.但是
shared_ptr可以调用get多次. - std::shared_future
是copyable的.但是访问同一个变量还是会带来竞争问题.但是你每一个线程拿一份
shared_future 的拷贝就不会出现这个问题了.
std::promise<int> p;
std::future<int> f(p.get_future());
assert(f.valid()); //<-- 1 the future f is valid
std::shared_future<int> sf(std::move(f));
assert(!f.valid()); //<-- 2 f is no longer valid
assert(sf.valid()); //<-- 3 sf is now valid
//注意,如果要用一个共享future引用住一个future对象请使用move,其他方式时非法的。因为future时not copyable的对象
你可以这样来创建 shared_future
std::promise<std::string> p;//<--1 Implicit transfer of ownership
std::shared_future<std::string> sf(p.get_future());
//右值,可以隐式转换.
你还可以这样创建当然这需要C++14的支持.
std::promise< std::map< SomeIndexType, SomeDataType, SomeComparator,
SomeAllocator>::iterator> p;
auto sf=p.get_future().share();
//(future含有一个share成员函数,可以很方便的创建share_ptr)