作为Boost.Asio的初学者,我对io_service::run()
感到困惑。如果有人能够解释这个方法阻止/解除阻塞的话,我将不胜感激。文件说明:
run()
函数阻塞,直到所有工作完成,并且不再需要调度处理程序,或者直到io_service
已停止。多个线程可以调用
run()
函数来设置一个线程池,io_service
可以从中执行处理程序。在池中等待的所有线程都是等效的,io_service
可以选择其中任何一个来调用处理程序。
run()
函数的正常退出意味着io_service
对象已停止(stopped()
函数返回true)。除非事先致电run()
,否则对[{1}},run_one()
,poll()
或poll_one()
的后续调用将立即返回。
以下陈述是什么意思?
[......]不再需要派遣处理人员[...]
在尝试理解reset()
的行为时,我遇到了这个example(例3a)。在其中,我观察到io_service::run()
阻塞并等待工单。
io_service->run()
但是,在我正在使用的以下代码中,客户端使用TCP / IP连接并运行方法阻塞,直到异步接收数据。
// 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();
对于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);
中描述其在下面两个示例中的行为的任何解释都将不胜感激。
答案 0 :(得分:205)
让我们从一个简化的例子开始,检查相关的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
处理程序只不过是一个回调。在示例代码中,有3个处理程序:
print
处理程序(1)。handle_async_receive
处理程序(3)。print
处理程序(4)。即使使用两次相同的print()
函数,每次使用都被认为是创建自己的唯一可识别处理程序。处理程序可以有多种形状和大小,从上面的基本函数到更复杂的构造,如boost::bind()
和lambdas生成的函子。无论复杂程度如何,处理程序仍然只是一个回调。
工作是代表应用程序代码请求Boost.Asio进行的一些处理。有时Boost.Asio可能会在它被告知之后立即开始一些工作,有时它可能会等待稍后的工作。完成工作后,Boost.Asio将通过调用提供的处理程序来通知应用程序。
Boost.Asio保证处理程序只能在当前正在调用run()
,run_one()
,poll()
或poll_one()
的线程中运行。这些线程将起作用并调用处理程序。因此,在上面的示例中,print()
在发布到io_service
(1)时不会被调用。相反,它会添加到io_service
并在稍后的时间点调用。在这种情况下,它在io_service.run()
(5)。
asynchronous operation创建工作,Boost.Asio将调用处理程序,以在工作完成时通知应用程序。通过调用名称前缀为async_
的函数来创建异步操作。这些功能也称为启动功能。
异步操作可以分解为三个独特的步骤:
io_service
。 async_receive
操作(3)通知io_service
它需要异步读取套接字中的数据,然后async_receive
立即返回。socket
接收数据时,将读取字节并将其复制到buffer
。实际工作将在以下任何一项中完成:
io_service
(5)。handle_async_receive
ReadHandler。再次,处理程序仅在运行io_service
的线程中调用。因此,无论工作何时完成(3或5),都可以保证只在handle_async_receive()
(5)内调用io_service.run()
。这三个步骤之间的时间和空间分离称为控制流反转。这是使异步编程变得困难的复杂性之一。但是,有些技术可以帮助缓解这种情况,例如使用coroutines。
io_service.run()
做什么?当一个线程调用io_service.run()
时,将从该线程中调用work和处理程序。在上面的示例中,io_service.run()
(5)将阻止,直到:
print
处理程序调用并返回,接收操作以成功或失败完成,并且已调用并返回其handle_async_receive
处理程序。io_service
通过io_service::stop()
明确停止。一种潜在的假性流可以描述如下:
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
注意读取完成后,它向io_service
添加了另一个处理程序。这个细微的细节是异步编程的一个重要特征。它允许将处理程序链接在一起。例如,如果handle_async_receive
未获得预期的所有数据,则其实现可能会发布另一个异步读取操作,导致io_service
有更多工作,因此不会从io_service.run()
返回。
请注意,当io_service
用完时,应用程序必须reset()
io_service
才能再次运行。
现在,让我们检查一下问题中引用的两段代码。
socket->async_receive
将工作添加到io_service
。因此,io_service->run()
将阻塞,直到读取操作成功或错误完成,ClientReceiveEvent
已完成运行或抛出异常。
为了使其更容易理解,这里有一个较小的注释示例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
}
在高级别,程序将创建2个线程来处理io_service
的事件循环(2)。这导致了一个简单的线程池,它将计算Fibonacci数(3)。
问题代码与此代码之间的一个主要区别是,此代码在实际工作之前调用io_service::run()
(2)并将处理程序添加到io_service
(3 )。为防止io_service::run()
立即返回,将创建io_service::work
对象(1)。此对象可防止io_service
停止工作;因此,io_service::run()
不会因为没有工作而返回。
整体流程如下:
io_service::work
添加的io_service
对象。io_service::run()
。由于io_service
对象,这些工作线程不会从io_service::work
返回。io_service
,然后立即返回。工作线程,而不是主线程,可能会立即开始运行这些处理程序。io_service::work
对象。io_service
既没有处理程序也没有工作。代码可以采用与原始代码相同的方式编写,其中处理程序添加到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
}
虽然问题中的代码使用了异步操作,但它正在有效地同步运行,因为它正在等待异步操作完成:
socket.async_receive(buffer, handler)
io_service.run();
相当于:
boost::asio::error_code error;
std::size_t bytes_transferred = socket.receive(buffer, 0, error);
handler(error, bytes_transferred);
作为一般经验法则,尽量避免混合同步和异步操作。通常,它可以将复杂的系统变成复杂的系统。这个answer强调了异步编程的优点,其中一些也包含在Boost.Asio documentation中。
答案 1 :(得分:16)
为了简化run
的工作方式,将其视为必须处理一堆纸的员工;它只需要一张纸,就可以完成纸张所说的内容,将纸张扔掉并取出下一页;当他用完床单时,就会离开办公室。在每张纸上都可以有任何类型的指令,甚至可以添加新的纸张。
回到asio:您可以通过两种方式提供io_service
工作,主要是:在您链接的示例中使用post
,或者使用内部调用post
的其他对象在io_service
上,与socket
及其async_*
方法一样。