使用异步操作时为什么没有堆栈溢出?

时间:2016-03-01 15:56:27

标签: c++11 asynchronous recursion boost-asio

我正在使用Boost.Asio来学习异步操作。在阅读了很多关于这个概念的文章之后,我仍然感到困惑的是为什么这段代码是Asio Docs的C ++ 11例子的一部分并没有使堆栈溢出?我无法想象这个地方代码的流程。它看起来很递归,因为ui-sref一次又一次地调用自己......我可以想象这个堆栈适用于20个客户端,但适用于2 000个客户端?我认为对于异步操作,将do_accept();放在循环中并且在其中没有递归调用是更清楚的。它的工作方式是否相同?

我添加了类成员acceptor_.async_accept()用于测试目的。

代码:

call_nr

4 个答案:

答案 0 :(得分:2)

回调!=递归模式。

在场景背后,Boost.Asio使用一些轮询对象,通知程序某些异步IO操作已完成。
在Windows上,我们谈论的是 IO完成端口
在Linux上,我们谈论 epoll

当异步IO操作完成时,轮询对象会轮询一些控制块,其中包含有关IO操作的详细信息。
在Windows上我们谈论的是OVERLAPPED结构 在Linux上,我们讨论的是aiocb结构。

Boost.Asio(以及其他平台,如libuv / node.js,.Net,Java等)继承自该控制块并向其添加其他内容。 Boost.Asio添加了回调对象。

因此,当轮询对象轮询控制块时,Boost.Asio会拉回调并运行它。它可以使用另一个回调启动另一个异步IO操作,但是当原始回调完成时,堆栈将返回给调用者。该功能不会长时间停留 - 即使启动了另一个IO,功能也会继续完成。

流程可能如下所示:

1) execute function A which reads file async
2) an asynchonous IO action is launched with a callback B 
3) A continues to execute. it does not block since this is an async IO action
4) A finiehs to run and returns to the caller
5) some time later , the async IO finishes
6) IOCP/epoll polls the control block
7) the IOCP/epoll thread pulls callback B and execute it
8) callback B launches a new async IO action with callback C
9) B continues to execute, since the async IO doesn't block
10) B finished to run and returns to the caller
11) some time later, the Async IO is finished ... and so one

了解更多详情,请阅读 reactor pattern

答案 1 :(得分:1)

因为您正在创建的lambda表达式被保存为仿函数并存储在与io_service对象关联的数据结构中。

当您调用async_accept时,该调用立即返回。它不会创建一个新的堆栈帧(在语义上。从技术上讲,它确实如此,但它只存在几分之一毫秒)

当最终调用lambda表达式时,它将位于具有io_service的任何关联线程的堆栈上,而不是原始堆栈。

答案 2 :(得分:1)

只有同步调用才会增加堆栈。

使用acceptor_.async_accept,您传递的回调函数是要求系统稍后调用

重要的是,它不会立即被调用,因此不会从你在堆栈中的位置被调用。

如果你在lambda函数中放置一个断点并检查它的堆栈,你会注意到它是从其他地方调用的(在实现acceptor_的代码中)。

因此,当它稍后运行并再次调用do_accept时,它就是新堆栈。

这在传统意义上不是技术上的递归。它只是一个异步的“循环”,它只会反复调用函数,但是每次从一个时间点开始,并且不需要堆栈累积来实现它,所以它可以永远持续下去。 / p>

答案 3 :(得分:0)

我绝不是Boost.Asio的专家。

控制流程如下:

  1. do_accept被调用。
  2. 使用 lambda 调用
  3. acceptor_.async_accept,我们称之为 L
  4. acceptor_.async_accept返回 L
  5. do_accept返回。
  6. 根据连接请求,发生这种情况

    1. L 被调用。
    2. 再次调用
    3. do_accept。从1开始重复。
    4. 如您所见,第二次调用do_accept时,前一个调用已经完成。

      如果acceptor_.async_accept在返回之前调用了 L ,那么确实会有一个可能导致堆栈溢出的递归。

      您应该查看acceptor_.async_accept的来源,您应该看到,即使在执行acceptor_.async_accept期间发生连接请求, L 也不会处理它。
      那就是acceptor_.async_accept和调用 L 的代码之间存在同步机制。