繁体   English   中英

为什么在使用boost :: asio时需要每个连接使用绞线?

[英]Why do I need strand per connection when using boost::asio?

我正在Boost的网站上查看HTTP Server 3示例。

你们能解释一下为什么每个连接都需要strand吗? 如我所见,我们仅在read-event的处理程序中调用read_some 因此,基本上read_some调用是顺序的,因此不需要进行绞合( 第3款的第2项也是如此)。 多线程环境中的风险在哪里?

该文档是正确的。 使用半双工协议实现(例如HTTP Server 3)时 ,不需要该子strand 调用链可以说明如下:

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_上同时执行处理程序或操作, socket_它是在隐式链中运行。


线程安全

尽管在示例中它本身并不构成问题,但我想强调一下线程和组合操作的一个重要细节,例如boost::asio::async_write 在解释细节之前,让我们首先使用Boost.Asio涵盖线程安全模型。 对于大多数Boost.Asio对象,在一个对象上进行多个异步操作是安全的。 仅指定对对象的并发调用是不安全的。 在下面的图中,每列代表一个线程,每行代表一个线程在某个时刻在做什么。

一个线程进行顺序调用是安全的,而其他线程则不执行:

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

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

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

但是,多个线程同时进行调用并不安全1

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

股线

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

  • strand.wrap包装处理程序。 这将返回一个新的处理程序,该处理程序将通过链进行分派。
  • 直接通过子线发布调度

组合操作是唯一的,因为对流的中间调用是在处理程序的strand(如果存在)中调用的,而不是在其中启动组合操作的strand中调用。 与其他操作相比,这代表了指定链的位置。 这是一些专注于链用法的示例代码,它将演示一个套接字,该套接字是通过非组合操作读取的,并与组合操作同时写入。

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()钩子的类型上,例如,使用strand.wrap的返回类型构造boost::functionstrand.wrap ,则中间处理程序将在该链之外执行,并且只有完成处理程序将在该链中执行。 有关更多详细信息,请参见此答案。

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

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

在以下代码中,仅完成处理程序将在该链中执行。 没有任何中间处理程序将在该链中执行:

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

1. 修订历史记录了此规则的异常。 read, write, accept, and connection operations are thread safe. 如果操作系统支持,则读,写,接受和连接操作是线程安全的。 为了完整起见,我将其包括在此处,但建议谨慎使用。

我相信是因为组合操作async_write async_write由多个socket :: async_write_some异步组成。 Strand有助于序列化这些操作。 asio的作者Chris Kohlhoff在1:17左右的boostcon演讲中简短地谈到了这一点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM