当你编写一个需要处理很多网络连接的程序时,你往往倾向于将在每个线程中处理一个连接,这样更容易设计和实现。这比较适合少量连接的情况。随着连接数的增加,这变得越来越不可能,太多的线程消耗过多的系统资源,以及潜在的引发频繁的上下文切换(当线程数操作可硬件能提供的最大并发数时),影响效率。在极端的情况下,可能将操作系统资源耗尽。
在这种维护大量网络连接的应用中,通常的做法是使用少量的线程来处理连接,每个线程一次处理逗哥网络连接。考虑一个这样的线程,来自不同连接的数据包是无序的,同样,这些数据包也被无序的发送出去。在大多数情况下,程序中的其他部分将一直等待数据被成功发送出去,或新的数据被成功接收。
std::promise<T>提供一种设置值的方式,稍后可以通过一个与之关联的std::future<T>来获取这个值。可以通过promise的函数 get_future()来得到future对象,当promise对象使用 set_value()函数设置值时,future的状态被设置为ready,可以立刻获取其保存的值。如果没有设置值就销毁一个promise对象,那么存储在future中将不是值而是一个异常,4.2.4节将介绍如何在线程中传递异常。
下面的示例演示了这种机制的一种实现方式。使用promise/future对来标识传出的数据块是否成功传递,future的值使用简单的成功/失败标识。对于传入数据,future的值代表有效载荷。
void process_connections(connection_set& connections)
{
while (!done(connections))
{
for (connection_iterator connection = connections.begin(), end = connections.end();connection != end;++connection)
{
if (connection->has_incoming_data())
{
data_packet data = connection->incoming();
std::promise<payload_type>& p =
connection->get_promise(data.id);
p.set_value(data.payload);
}
if (connection->has_outgoing_data())
{
outgoing_packet data = connection->top_of_outgoing_queue();
connection->send(data.payload);
data.promise.set_value(true);
}
}
}
}
process_connections()函数周期检测是否需要接收数据或者发送数据。它假设,每一个传入的数据包中都包含一个数据ID和一个payload,ID关联到一个promise对象上,值被设置为payload。对于数据发送,每个即将发送的数据包来自一个队列,并需要通过connection发送。一旦数据发送成功,则与数据关联的promise被设置为true,标识出发送成功。这种机制是否适合网络通讯取决于具体的网络协议,也许在特殊的场合这种promise/future无法正常工作,虽然它类似某些操作系统的异步I/O结构。
到目前为止,我们一直没有考虑异常的发生。但是事情不总是完全按照我们的想象去发展,有时硬盘满了,有时你寻找的对象不在那里,有时网络出问题了,有时数据库崩溃了。如果你在一个线程中做一些操作,并且需要返回结果,那么代码应该能够记录一个异常导致的错误,所以你完全不能认为每件事都进行的十分完美,因为你想使用std::packaged_task或者std::promise。C++标准库提供了在这种情况下用来干净的处理异常的方法,允许将异常作为关联结果的一部分保存起来。
将一个异常保存在future中
观察下面的小程序,一个求平方根的函数,如果试图传入一个负数,则抛出一个异常:
double square_root(double x)
{
if (x<0)
{
throw std::out_of_range(“x<0”);
}
return sqrt(x);
}
现在想象一下,如果你在线程中执行这个函数:
std::future<double> f=std::async(square_root,-1);
double y=f.get();
那么情况是这样的,当线程函数内抛出异常时,它将这个异常存入future中,而不是值,并且future状态变为ready。当外部调用future时,这个异常被再次抛出。对于packaged_task也一样。
同样,promise也提供相同的手段,你可以通过 set_exception()设置一个异常。可以这样使用:
extern std::promise<double> some_promise;
try
{
some_promise.set_value(calculate_value());
}
catch (...)
{
some_promise.set_exception(std::current_exception());
}
这里使用了 std::current_exception()函数来获取当前的异常;另一种做法是,使用 std::copy_exception()在不抛出异常的情况下保存一个新的异常:
some_promise.set_exception(std::make_exception_ptr(std::logic_error("foo ")));//书是说的是copy_exception,现在C++11已经将其改为make_exception_ptr
如果异常的类型是已知的,这将是一种比try/catch块更简洁的使用方式;这不仅仅是因为它的代码更简洁,更是因为它给编译器更多优化的机会。
另一种在future中保存异常的方式是销毁一个没有设置值的promise对象,或者是销毁一个没有执行动作的packaged_task对象。这两种情况下,一个带有 std::future_errc::broken_promise错误码的std::future_error异常将被保存在没有ready的future中;通过创建一个future你期待得到一个值或者一个异常,如果销毁了为它提供值的源头,或者在为其赋值前发生异常,那就相当于令这个期待破产了。如果编译器没有为一个future设置一个值或者一个异常,那么等待它的线程将永远等待下去。