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
Confused when boost::asio::io_service run method blocks/unblocks
提问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 theio_service
has been stopped.Multiple threads may call the
run()
function to set up a pool of threads from which theio_service
may execute handlers. All threads that are waiting in the pool are equivalent and theio_service
may choose any one of them to invoke a handler.A normal exit from the
run()
function implies that theio_service
object is stopped (thestopped()
function returns true). Subsequent calls torun()
,run_one()
,poll()
orpoll_one()
will return immediately unless there is a prior call toreset()
.
该
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
print
handler (1). - The
handle_async_receive
handler (3). - The
print
handler (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_service
and 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_service
that works needs to be done. Theasync_receive
operation (3) informs theio_service
that it will need to asynchronously read data from the socket, thenasync_receive
returns immediately. - Doing the actual work. In this case, when
socket
receives data, bytes will be read and copied intobuffer
. 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_receive
ReadHandler. Once again, handlersare only invoked within threads running theio_service
. Thus, regardless of when the work is done (3 or 5), it is guaranteed thathandle_async_receive()
will only be invoked withinio_service.run()
(5).
- 启动或通知
io_service
需要完成的相关工作。该async_receive
操作(3)通知io_service
,它需要异步读取数据从插座,然后async_receive
立即返回。 - 做实际工作。在这种情况下,当
socket
接收数据时,字节将被读取并复制到buffer
. 实际工作将在以下任一方式完成:- 发起函数(3),如果Boost.Asio可以确定它不会阻塞。
- 当应用程序显式运行
io_service
(5) 时。
- 调用
handle_async_receive
ReadHandler。再一次,处理程序仅在运行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
print
handlers, the receive operation completes with success or failure, and itshandle_async_receive
handler has been invoked and returned. - The
io_service
is explicitly stopped viaio_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_receive
did not get all the data it expected, then its implementation could post another asynchronous read operation, resulting in io_service
having 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_service
has ran out of work, the application must reset()
the io_service
before 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_receive
adds work to the io_service
. Thus, io_service->run()
will block until the read operation completes with success or error, and ClientReceiveEvent
has 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::work
object is created (1). This object prevents the io_service
from running out of work; therefore, io_service::run()
will not return as a result of no work.
问题代码和此代码之间的一个主要区别是此代码在实际工作和处理程序添加到(3)之前调用io_service::run()
(2 )。为了防止立即返回,创建了一个对象 (1)。此对象可防止工作耗尽;因此,不会因为没有工作而返回。io_service
io_service::run()
io_service::work
io_service
io_service::run()
The overall flow is as follows:
整体流程如下:
- Create and add the
io_service::work
object added to theio_service
. - Thread pool created that invokes
io_service::run()
. These worker threads will not return fromio_service
because of theio_service::work
object. - 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. - Delete the
io_service::work
object. - Wait for worker threads to finish running. This will only occur once all 3 handlers have finished execution, as the
io_service
neither has handlers nor work.
- 创建并添加
io_service::work
添加到io_service
. - 创建的线程池调用
io_service::run()
.io_service
由于io_service::work
对象的原因,这些工作线程将不会返回。 - 将 3 个计算斐波那契数的处理程序添加到
io_service
,并立即返回。工作线程,而不是主线程,可能会立即开始运行这些处理程序。 - 删除
io_service::work
对象。 - 等待工作线程完成运行。这只会在所有 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_service
event 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 run
does, 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_service
work in two ways, essentially: by using post
on it as in the sample you linked, or by using other objects that internally call post
on the io_service
, like the socket
and its async_*
methods.
为了简化工作原理run
,可以将其视为必须处理一堆纸的员工;它需要一张纸,按照它所说的去做,把它扔掉,然后拿走下一张;当他用完床单时,它就会离开办公室。每张纸上都可以有任何类型的说明,甚至可以在纸堆中添加一张新纸。回到ASIO:你可以给一个io_service
工作在两个方面,主要有:通过使用post
它作为你的联系,在样品中或通过使用内部调用其他物体post
上io_service
,比如socket
和async_*
方法。