为什么在使用 boost::asio 时每个连接都需要链? [英] Why do I need strand per connection when using boost::asio?

查看:31
本文介绍了为什么在使用 boost::asio 时每个连接都需要链?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在审核 HTTP 服务器 3 Boost 网站上的示例.

I'm reviewing HTTP Server 3 example on Boost's website.

你们能解释一下为什么每个连接都需要 strand 吗?正如我所看到的,我们只在读取事件的处理程序中调用 read_some.所以基本上 read_some 调用是连续的,因此不需要链(和 第 3 段的第 2 项 说同样的话).多线程环境的风险在哪里?

Could you guys please explain why I need strand per connection ? As I can see we call read_some only in handler of read-event. So basically read_some calls are sequential therefore there is no need for strand (and item 2 of 3rd paragraph says the same thing). Where is the risk in multi-threading environment?

推荐答案

文档是正确的.使用半双工协议实现,例如 HTTP Server 3strand 不是必需的.调用链可以说明如下:

The documentation is correct. With a half duplex protocol implementation, such as HTTP Server 3, the strand is not necessary. The call chains can be illustrated as follows:

void connection::start()
{
  socket.async_receive_from(..., &handle_read);  ----.
}                                                    |
    .------------------------------------------------'
    |      .-----------------------------------------.
    V      V                                         |
void connection::handle_read(...)                    |
{                                                    |
  if (result)                                        |
    boost::asio::async_write(..., &handle_write); ---|--.
  else if (!result)                                  |  |
    boost::asio::async_write(..., &handle_write);  --|--|
  else                                               |  |
    socket_.async_read_some(..., &handle_read);  ----'  |
}                                                       |
    .---------------------------------------------------'
    |
    V
void handle_write(...)

如图所示,每个路径只启动一个异步事件.由于不可能在 socket_ 上并发执行处理程序或操作,因此据说它在隐式链中运行.

As shown in the illustration, only a single asynchronous event is started per path. With no possibility of concurrent execution of the handlers or operations on socket_, it is said to be running in an implicit strand.

虽然它在示例中没有作为问题出现,但我想强调链和组合操作的一个重要细节,例如 boost::asio::async_write.在解释细节之前,让我们首先介绍一下 Boost.Asio 的线程安全模型.对于大多数 Boost.Asio 对象,在一个对象上挂起多个异步操作是安全的;只是指定对象上的并发调用是不安全的.在下图中,每一列代表一个线程,每条线代表一个线程在某一时刻正在做什么.

While it does not present itself as an issue in the example, I would like to highlight one important detail of strands and composed operations, such as boost::asio::async_write. Before explaining the details, lets first cover the thread safety model with Boost.Asio. For most Boost.Asio objects, it is safe to have multiple asynchronous operations pending on an object; it is just specified that concurrent calls on the object are unsafe. In the diagrams below, each column represents a thread and each line represents what a thread is doing at a moment in time.

单个线程进行顺序调用而其他线程不进行调用是安全的:

It is safe for a single thread to make sequential calls while other threads make none:

 thread_1                             | thread_2
--------------------------------------+---------------------------------------
socket.async_receive(...);            | ...
socket.async_write_some(...);         | ...

多线程调用是安全的,但不能同时调用:

It is safe for multiple threads to make calls, but not concurrently:

 thread_1                             | thread_2
--------------------------------------+---------------------------------------
socket.async_receive(...);            | ...
...                                   | socket.async_write_some(...);

但是,多个线程并发调用是不安全的1:

However, it is not safe for multiple threads to make calls concurrently1:

 thread_1                             | thread_2
--------------------------------------+---------------------------------------
socket.async_receive(...);            | socket.async_write_some(...);
...                                   | ...

为了防止并发调用,处理程序通常从链中调用.这是通过以下任一方式完成的:

Strands

To prevent concurrent invocations, handlers are often invoked from within strands. This is done by either:

组合操作的独特之处在于对 stream 的中间调用是在 handler 的链中调用的(如果存在),而不是在组合的链中调用操作启动.与其他操作相比,这代表了指定链的位置的反转.下面是一些专注于链使用的示例代码,它将演示通过非组合操作读取的套接字,并与组合操作并发写入.

Composed operations are unique in that intermediate calls to the stream are invoked within the handler's strand, if one is present, instead of the strand in which the composed operation is initiated. When compared to other operations, this presents an inversion of where the strand is specified. Here is some example code focusing on strand usage, that will demonstrate a socket that is read from via a non-composed operation, and concurrently written to with a composed operation.

void start()
{
  // Start read and write chains.  If multiple threads have called run on
  // the service, then they may be running concurrently.  To protect the
  // socket, use the strand.
  strand_.post(&read);
  strand_.post(&write);
}

// read always needs to be posted through the strand because it invokes a
// non-composed operation on the socket.
void read()
{
  // async_receive is initiated from within the strand.  The handler does
  // not affect the strand in which async_receive is executed.
  socket_.async_receive(read_buffer_, &handle_read);
}

// This is not running within a strand, as read did not wrap it.
void handle_read()
{
  // Need to post read into the strand, otherwise the async_receive would
  // not be safe.
  strand_.post(&read);
}

// The entry into the write loop needs to be posted through a strand.
// All intermediate handlers and the next iteration of the asynchronous write
// loop will be running in a strand due to the handler being wrapped.
void write()
{
  // async_write will make one or more calls to socket_.async_write_some.
  // All intermediate handlers (calls after the first), are executed
  // within the handler's context (strand_).
  boost::asio::async_write(socket_, write_buffer_,
                           strand_.wrap(&handle_write));
}

// This will be invoked from within the strand, as it was a wrapped
// handler in write().
void handle_write()
{
  // handler_write() is invoked within a strand, so write() does not
  // have to dispatched through the strand.
  write();
}

<小时>

处理程序类型的重要性

此外,在组合操作中,Boost.Asio 使用 参数相关查找 (ADL)通过完成处理程序的链调用中间处理程序.因此,完成处理程序的类型具有适当的 asio_handler_invoke() 钩子.如果类型擦除发生在没有适当 asio_handler_invoke() 钩子的类型上,例如 boost::function 是从 boost::function 的返回类型构造的情况code>strand.wrap,那么中间处理程序将在链外执行,只有完成处理程序将在链内执行.有关详细信息,请参阅答案.


Importance of Handler Types

Also, within composed operations, Boost.Asio uses argument dependent lookup (ADL) to invoke intermediate handlers through the completion handler's strand. As such, it is important that the completion handler's type has the appropriate asio_handler_invoke() hooks. If type erasure occurs to a type that does not have the appropriate asio_handler_invoke() hooks, such as a case where a boost::function is constructed from the return type of strand.wrap, then intermediate handlers will execute outside of the strand, and only the completion handler will execute within the strand. See this answer for more details.

在以下代码中,所有中间处理程序和完成处理程序都将在链中执行:

In the following code, all intermediate handlers and the completion handler will execute within the strand:

boost::asio::async_write(stream, buffer, strand.wrap(&handle_write));

在下面的代码中,只有完成处理程序将在链中执行.任何中间处理程序都不会在链中执行:

In the following code, only the completion handler will execute within the strand. None of the intermediate handlers will execute within the strand:

boost::function<void()> handler(strand.wrap(&handle_write));
boost::asio::async_write(stream, buffer, handler);

<小时>

1.修订历史记录此规则的异常.如果操作系统支持,同步读、写、接受和连接操作都是线程安全的.为了完整起见,我将其包含在此处,但建议谨慎使用.


1. The revision history documents an anomaly to this rule. If supported by the OS, synchronous read, write, accept, and connection operations are thread safe. I an including it here for completeness, but suggest using it with caution.

这篇关于为什么在使用 boost::asio 时每个连接都需要链?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆