C++ 当 boost::asio::io_service 运行方法阻塞/解除阻塞时感到困惑

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15568100/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 19:29:16  来源:igfitidea点击:

Confused when boost::asio::io_service run method blocks/unblocks

c++boost-asio

提问by MistyD

Being a total beginner to Boost.Asio, I am confused with io_service::run(). I would appreciate it if someone could explain to me when this method blocks/unblocks. The documentations states:

作为 Boost.Asio 的初学者,我对io_service::run(). 如果有人可以向我解释此方法何时阻止/解除阻止,我将不胜感激。文件指出:

The run()function blocks until all work has finished and there are no more handlers to be dispatched, or until the io_servicehas been stopped.

Multiple threads may call the run()function to set up a pool of threads from which the io_servicemay execute handlers. All threads that are waiting in the pool are equivalent and the io_servicemay choose any one of them to invoke a handler.

A normal exit from the run()function implies that the io_serviceobject is stopped (the stopped()function returns true). Subsequent calls to run(), run_one(), poll()or poll_one()will return immediately unless there is a prior call to reset().

run()函数会阻塞,直到所有工作完成并且没有更多的处理程序要调度,或者直到io_service已停止。

多个线程可以调用该run()函数来建立一个线程池,从中io_service可以执行处理程序。在池中等待的所有线程都是等效的,并且io_service可以选择其中的任何一个来调用处理程序。

run()函数的正常退出意味着io_service对象已停止(stopped()函数返回 true)。后续调用run()run_one()poll()poll_one()将除非有预先调用立即返回reset()

What does the following statement mean?

以下声明是什么意思?

[...] no more handlers to be dispatched [...]

[...] 不再需要派遣处理程序 [...]



While trying to understand the behavior of io_service::run(), I came across this example(example 3a). Within it, I observe that io_service->run()blocks and waits for work orders.

在尝试理解 的行为时io_service::run(),我遇到了这个示例(示例 3a)。在其中,我观察到io_service->run()阻塞并等待工单。

// WorkerThread invines io_service->run()
void WorkerThread(boost::shared_ptr<boost::asio::io_service> io_service);
void CalculateFib(size_t);

boost::shared_ptr<boost::asio::io_service> io_service(
    new boost::asio::io_service);
boost::shared_ptr<boost::asio::io_service::work> work(
   new boost::asio::io_service::work(*io_service));

// ...

boost::thread_group worker_threads;
for(int x = 0; x < 2; ++x)
{
  worker_threads.create_thread(boost::bind(&WorkerThread, io_service));
}

io_service->post( boost::bind(CalculateFib, 3));
io_service->post( boost::bind(CalculateFib, 4));
io_service->post( boost::bind(CalculateFib, 5));

work.reset();
worker_threads.join_all();

However, in the following code that I was working on, the client connects using TCP/IP and the run method blocks until data is asynchronously received.

但是,在我正在处理的以下代码中,客户端使用 TCP/IP 进行连接,并且 run 方法会阻塞,直到异步接收到数据。

typedef boost::asio::ip::tcp tcp;
boost::shared_ptr<boost::asio::io_service> io_service(
    new boost::asio::io_service);
boost::shared_ptr<tcp::socket> socket(new tcp::socket(*io_service));

// Connect to 127.0.0.1:9100.
tcp::resolver resolver(*io_service);
tcp::resolver::query query("127.0.0.1", 
                           boost::lexical_cast< std::string >(9100));
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
socket->connect(endpoint_iterator->endpoint());

// Just blocks here until a message is received.
socket->async_receive(boost::asio::buffer(buf_client, 3000), 0,
                      ClientReceiveEvent);
io_service->run();

// Write response.
boost::system::error_code ignored_error;
std::cout << "Sending message \n";
boost::asio::write(*socket, boost::asio::buffer("some data"), ignored_error);

Any explanation of run()that describes its behavior in the two examples below would be appreciated.

run()在下面的两个示例中描述其行为的任何解释都将不胜感激。

回答by Tanner Sansbury

Foundation

基础

Lets start with a simplified example and examine the relevant Boost.Asio pieces:

让我们从一个简化的示例开始并检查相关的 Boost.Asio 部分:

void handle_async_receive(...) { ... }
void print() { ... }

...  

boost::asio::io_service io_service;
boost::asio::ip::tcp::socket socket(io_service);

...

io_service.post(&print);                             // 1
socket.connect(endpoint);                            // 2
socket.async_receive(buffer, &handle_async_receive); // 3
io_service.post(&print);                             // 4
io_service.run();                                    // 5

What Is A Handler?

什么是处理程序

A handleris nothing more than a callback. In the example code, there are 3 handlers:

一个处理程序无非是一个回调。在示例代码中,有 3 个处理程序:

  • The printhandler (1).
  • The handle_async_receivehandler (3).
  • The printhandler (4).
  • print处理程序(1)。
  • handle_async_receive处理程序(3)。
  • print处理程序(4)。

Even though the same print()function is used twice, each use is considered to create its own uniquely identifiable handler. Handlers can come in many shapes and sizes, ranging from basic functions like the ones above to more complex constructs such as functors generated from boost::bind()and lambdas. Regardless of the complexity, the handler still remains nothing more than a callback.

即使同一个print()函数被使用两次,每次使用都被认为是创建自己唯一可识别的处理程序。处理程序可以有多种形状和大小,从上面的基本函数到更复杂的构造,例如从boost::bind()lambda 和 lambda生成的函子。不管复杂性如何,处理程序仍然只是一个回调。

What Is Work?

什么是工作

Work is some processing that Boost.Asio has been requested to do on behalf of the application code. Sometimes Boost.Asio may start some of the work as soon as it has been told about it, and other times it may wait to do the work at a later point in time. Once it has finished the work, Boost.Asio will inform the application by invoking the supplied handler.

工作是 Boost.Asio 被要求代表应用程序代码进行的一些处理。有时 Boost.Asio 可能会在被告知后立即开始某些工作,而其他时候它可能会等待稍后再进行工作。一旦完成工作,Boost.Asio 将通过调用提供的处理程序通知应用程序

Boost.Asio guarantees that handlerswill only run within a thread that is currently calling run(), run_one(), poll(), or poll_one(). These are the threads that will do work and call handlers. Therefore, in above example, print()is not invoked when it is posted into the io_service(1). Instead, it is added to the io_serviceand will be invoked at a later point in time. In this case, it within io_service.run()(5).

Boost.Asio的保证了处理器将只当前调用线程中运行run()run_one()poll(),或poll_one()。这些是将执行工作并调用处理程序的线程。因此,在上面的例子中,print()当它被发布到io_service(1)时不会被调用。相反,它被添加到io_service并且将在稍后的时间点被调用。在这种情况下,它在io_service.run()(5)之内。

What Are Asynchronous Operations?

什么是异步操作?

An asynchronous operationcreates work and Boost.Asio will invoke a handlerto inform the application when the work has completed. Asynchronous operations are created by calling a function that has a name with the prefix async_. These functions are also known as initiating functions.

一个异步操作创建工作,Boost.Asio的将调用处理程序通知应用程序时的工作已经完成。异步操作是通过调用名称带有前缀的函数来创建的async_。这些功能也称为启动功能

Asynchronous operations can be decomposed into three unique steps:

异步操作可以分解为三个独特的步骤:

  • Initiating, or informing, the associated io_servicethat works needs to be done. The async_receiveoperation (3) informs the io_servicethat it will need to asynchronously read data from the socket, then async_receivereturns immediately.
  • Doing the actual work. In this case, when socketreceives data, bytes will be read and copied into buffer. The actual work will be done in either:
    • The initiating function (3), if Boost.Asio can determine that it will not block.
    • When the application explicitly run the io_service(5).
  • Invoking the handle_async_receiveReadHandler. Once again, handlersare only invoked within threads running the io_service. Thus, regardless of when the work is done (3 or 5), it is guaranteed that handle_async_receive()will only be invoked within io_service.run()(5).
  • 启动或通知io_service需要完成的相关工作。该async_receive操作(3)通知io_service,它需要异步读取数据从插座,然后async_receive立即返回。
  • 做实际工作。在这种情况下,当socket接收数据时,字节将被读取并复制到buffer. 实际工作将在以下任一方式完成:
    • 发起函数(3),如果Boost.Asio可以确定它不会阻塞。
    • 当应用程序显式运行io_service(5) 时。
  • 调用handle_async_receiveReadHandler。再一次,处理程序仅在运行io_service. 因此,无论工作何时完成(3 或 5),都保证handle_async_receive()只会在io_service.run()(5)内被调用。

The separation in time and space between these three steps is known as control flow inversion. It is one of the complexities that makes asynchronous programming difficult. However, there are techniques that can help mitigate this, such as by using coroutines.

这三个步骤之间的时间和空间分离称为控制流反转。这是使异步编程变得困难的复杂性之一。但是,有一些技术可以帮助缓解这种情况,例如使用协程

What Does io_service.run()Do?

io_service.run()什么?

When a thread calls io_service.run(), work and handlerswill be invoked from within this thread. In the above example, io_service.run()(5) will block until either:

当线程调用 时io_service.run(),将从该线程内调用工作和处理程序。在上面的例子中,io_service.run()(5) 将阻塞直到:

  • It has invoked and returned from both printhandlers, the receive operation completes with success or failure, and its handle_async_receivehandler has been invoked and returned.
  • The io_serviceis explicitly stopped via io_service::stop().
  • An exception is thrown from within a handler.
  • 它已从两个print处理程序调用和返回,接收操作以成功或失败完成,并且其handle_async_receive处理程序已被调用和返回。
  • 通过io_service显式停止io_service::stop()
  • 从处理程序中抛出异常。

One potential psuedo-ish flow could be described as the following:

一种潜在的伪流可以描述如下:

create io_service
create socket
add print handler to io_service (1)
wait for socket to connect (2)
add an asynchronous read work request to the io_service (3)
add print handler to io_service (4)
run the io_service (5)
  is there work or handlers?
    yes, there is 1 work and 2 handlers
      does socket have data? no, do nothing
      run print handler (1)
  is there work or handlers?
    yes, there is 1 work and 1 handler
      does socket have data? no, do nothing
      run print handler (4)
  is there work or handlers?
    yes, there is 1 work
      does socket have data? no, continue waiting
  -- socket receives data --
      socket has data, read it into buffer
      add handle_async_receive handler to io_service
  is there work or handlers?
    yes, there is 1 handler
      run handle_async_receive handler (3)
  is there work or handlers?
    no, set io_service as stopped and return

Notice how when the read finished, it added another handlerto the io_service. This subtle detail is an important feature of asynchronous programming. It allows for handlersto be chained together. For instance, if handle_async_receivedid not get all the data it expected, then its implementation could post another asynchronous read operation, resulting in io_servicehaving more work, and thus not returning from io_service.run().

请注意如何当读完成后,它增加了一个处理程序io_service。这个微妙的细节是异步编程的一个重要特征。它允许将处理程序链接在一起。例如,如果handle_async_receive没有得到它期望的所有数据,那么它的实现可能会发布另一个异步读取操作,导致io_service有更多的工作,因此不会从io_service.run().

Do note that when the io_servicehas ran out of work, the application must reset()the io_servicebefore running it again.

请注意,当io_service应用程序运行完毕后,应用程序必须reset()io_service再次运行它之前。



Example Question and Example 3a code

示例问题和示例 3a 代码

Now, lets examine the two pieces of code referenced in the question.

现在,让我们检查问题中引用的两段代码。

Question Code

问题代码

socket->async_receiveadds work to the io_service. Thus, io_service->run()will block until the read operation completes with success or error, and ClientReceiveEventhas either finished running or throws an exception.

socket->async_receive将工作添加到io_service. 因此,io_service->run()将阻塞直到读取操作以成功或错误完成,并且ClientReceiveEvent已完成运行或引发异常。

Example 3aCode

示例 3a代码

In hopes of making it easier to understand, here is a smaller annotated Example 3a:

为了让它更容易理解,这里有一个较小的带注释的示例 3a:

void CalculateFib(std::size_t n);

int main()
{
  boost::asio::io_service io_service;
  boost::optional<boost::asio::io_service::work> work =       // '. 1
      boost::in_place(boost::ref(io_service));                // .'

  boost::thread_group worker_threads;                         // -.
  for(int x = 0; x < 2; ++x)                                  //   :
  {                                                           //   '.
    worker_threads.create_thread(                             //     :- 2
      boost::bind(&boost::asio::io_service::run, &io_service) //   .'
    );                                                        //   :
  }                                                           // -'

  io_service.post(boost::bind(CalculateFib, 3));              // '.
  io_service.post(boost::bind(CalculateFib, 4));              //   :- 3
  io_service.post(boost::bind(CalculateFib, 5));              // .'

  work = boost::none;                                         // 4
  worker_threads.join_all();                                  // 5
}

At a high-level, the program will create 2 threads that will process the io_service's event loop (2). This results in a simple thread pool that will calculate Fibonacci numbers (3).

概括地说,程序将创建 2 个线程来处理io_service的事件循环 (2)。这会产生一个简单的线程池,用于计算斐波那契数 (3)。

The one major difference between the Question Code and this code is that this code invokes io_service::run()(2) beforeactual work and handlers are added to the io_service(3). To prevent the io_service::run()from returning immediately, an io_service::workobject is created (1). This object prevents the io_servicefrom running out of work; therefore, io_service::run()will not return as a result of no work.

问题代码和此代码之间的一个主要区别是此代码实际工作和处理程序添加到(3)之前调用io_service::run()(2 )。为了防止立即返回,创建了一个对象 (1)。此对象可防止工作耗尽;因此,不会因为没有工作而返回。io_serviceio_service::run()io_service::workio_serviceio_service::run()

The overall flow is as follows:

整体流程如下:

  1. Create and add the io_service::workobject added to the io_service.
  2. Thread pool created that invokes io_service::run(). These worker threads will not return from io_servicebecause of the io_service::workobject.
  3. Add 3 handlers that calculate Fibonacci numbers to the io_service, and return immediately. The worker threads, not the main thread, may start running these handlers immediately.
  4. Delete the io_service::workobject.
  5. Wait for worker threads to finish running. This will only occur once all 3 handlers have finished execution, as the io_serviceneither has handlers nor work.
  1. 创建并添加io_service::work添加到io_service.
  2. 创建的线程池调用io_service::run(). io_service由于io_service::work对象的原因,这些工作线程将不会返回。
  3. 将 3 个计算斐波那契数的处理程序添加到io_service,并立即返回。工作线程,而不是主线程,可能会立即开始运行这些处理程序。
  4. 删除io_service::work对象。
  5. 等待工作线程完成运行。这只会在所有 3 个处理程序都完成执行后发生,因为它们io_service既没有处理程序也没有工作。

The code could be written differently, in the same manner as the Original Code, where handlers are added to the io_service, and then the io_serviceevent loop is processed. This removes the need to use io_service::work, and results in the following code:

可以以与原始代码相同的方式编写不同的代码,其中将处理程序添加到io_service,然后io_service处理事件循环。这消除了使用的需要io_service::work,并产生以下代码:

int main()
{
  boost::asio::io_service io_service;

  io_service.post(boost::bind(CalculateFib, 3));              // '.
  io_service.post(boost::bind(CalculateFib, 4));              //   :- 3
  io_service.post(boost::bind(CalculateFib, 5));              // .'

  boost::thread_group worker_threads;                         // -.
  for(int x = 0; x < 2; ++x)                                  //   :
  {                                                           //   '.
    worker_threads.create_thread(                             //     :- 2
      boost::bind(&boost::asio::io_service::run, &io_service) //   .'
    );                                                        //   :
  }                                                           // -'
  worker_threads.join_all();                                  // 5
}


Synchronous vs. Asynchronous

同步与异步

Although the code in the question is using an asynchronous operation, it is effectively functioning synchronously, as it is waiting for the asynchronous operation to complete:

尽管问题中的代码使用异步操作,但它有效地同步运行,因为它正在等待异步操作完成:

socket.async_receive(buffer, handler)
io_service.run();

is equivalent to:

相当于:

boost::asio::error_code error;
std::size_t bytes_transferred = socket.receive(buffer, 0, error);
handler(error, bytes_transferred);

As a general rule of thumb, try to avoid mixing synchronous and asynchronous operations. Often times, it can turn a complex system into a complicated system. This answerhighlights advantages of asynchronous programming, some of which are also covered in the Boost.Asio documentation.

作为一般经验法则,尽量避免混合同步和异步操作。很多时候,它可以把一个复杂的系统变成一个复杂的系统。这个答案突出了异步编程的优势,其中一些也在 Boost.Asio文档中有所介绍。

回答by Loghorn

To simplify how what rundoes, think of it as an employee that must process a pile of paper; it takes one sheet, does what the sheet tells, throws the sheet away and takes the next one; when he runs out of sheets, it leaves the office. On each sheet there can be any kind of instruction, even adding a new sheet to the pile. Back to asio: you can give to an io_servicework in two ways, essentially: by using poston it as in the sample you linked, or by using other objects that internally call poston the io_service, like the socketand its async_*methods.

为了简化工作原理run,可以将其视为必须处理一堆纸的员工;它需要一张纸,按照它所说的去做,把它扔掉,然后拿走下一张;当他用完床单时,它就会离开办公室。每张纸上都可以有任何类型的说明,甚至可以在纸堆中添加一张新纸。回到ASIO:你可以给一个io_service工作在两个方面,主要有:通过使用post它作为你的联系,在样品中或通过使用内部调用其他物体postio_service,比如socketasync_*方法。