【问题标题】:Some clarification needed about synchronous versus asynchronous asio operations需要一些关于同步和异步 asio 操作的说明
【发布时间】:2011-07-14 01:12:49
【问题描述】:

据我所知,同步和异步操作之间的主要区别(即write()read() vs async_write()async_read())前者在操作完成之前不会返回 - 或错误,和后面的,立即返回。

由于异步操作由io_service.run() 控制,直到受控操作完成后才会完成。在我看来,在顺序操作中,与 TCP/IP 连接所涉及的协议(如 POP3)中的操作类似,其中操作是这样的顺序:

 C: <connect>
 S: Ok.
 C: User...
 S: Ok.
 C: Password
 S: Ok.
 C: Command
 S: answer
 C: Command
 S: answer
 ...
 C: bye
 S: <close>

同步/异步运算符的区别没有多大意义。

当然,在这两种操作中,总是存在程序流在某些情况下无限期停止的风险——比如使用计时器——,但我想知道在这件事上更权威的意见。

我必须承认这个问题的定义相当不明确,但我想听听一些关于何时使用其中一个或另一个的建议。我在使用 MS Visual Studio 调试我现在正在使用的 POP3 客户端中的异步 SSL 操作时遇到了问题,有时我认为在此使用异步可能是个坏主意。

【问题讨论】:

  • +1 甚至没有接近一个定义不明确的问题。异步编程很难,不要羞于提问。

标签: c++ asynchronous tcp boost-asio synchronous


【解决方案1】:

Boost.Asio documentation 在解释这两个概念方面做得非常出色。正如Ralf mentioned, Chris 也有一个很棒的博客来描述异步概念。 parking meter 示例解释超时如何工作特别有趣,bind illustrated 示例也是如此。

首先,考虑一个同步连接操作:

这里的控制流程相当简单,您的程序调用一些 API (1) 来连接套接字。 API 使用 I/O 服务 (2) 在操作系统 (3) 中执行操作。一旦此操作完成(4 和 5),控制权会立即返回到您的程序(6),并带有一些成功或失败的指示。

类似的异步操作有完全不同的控制流程:

在这里,您的应用程序使用相同的 I/O 服务 (2) 启动操作 (1),但控制流是相反的。操作的完成会导致 I/O 服务通过完成处理程序通知您的程序。从第 3 步到操作完成之间的时间完全包含在同步情况的连接操作中。

您可以看到,同步案例对于大多数程序员来说自然更容易掌握,因为它代表了传统的控制流范式。异步操作使用的反向控制流很难理解,它通常会迫使您的程序将操作拆分为 starthandle 方法,其中逻辑被转移。但是,一旦您对这个控制流有了基本的了解,您就会意识到这个概念的真正强大之处。异步编程的一些优点是:

  • 将线程与并发分离。进行长时间运行的操作,对于同步情况,您通常会创建一个单独的线程来处理该操作,以防止应用程序的 GUI 变得无响应。这个概念在小范围内运行良好,但在少数线程中很快就崩溃了。

  • 提高性能。每个连接的线程设计根本无法扩展。请参阅C10K problem

  • 组合(或链接)。更高级别的操作可以由多个完成处理程序组成。考虑传输 JPEG 图像,协议可能规定前 40 个字节包括一个描述图像大小、形状、可能还有其他一些信息的标头。发送此标头的第一个完成处理程序可以启动第二个操作以发送图像数据。更高级别的操作sendImage() 不需要知道或关心用于实现数据传输的方法链接。

  • 超时和取消能力。有一些特定于平台的方法可以使长时间运行的操作超时(例如:SO_RCVTIMEOSO_SNDTIMEO)。使用异步操作可以使用 deadline_timer 在所有支持的平台上取消长时间运行的操作。


当然,在这两种操作中都有 程序流动的风险始终存在 一些人无限期地停止 情况 - 那里使用 计时器-,但我想知道一些 更多权威意见在此 问题。

我使用 Asio 的个人经验源于可扩展性方面。为supercomputers 编写软件时,在处理有限的资源(例如内存、线程、套接字等)时需要相当小心。使用每个连接的线程进行约 200 万个同时操作是一种在到达时就失效的设计。

【讨论】:

  • 尽管使用它的时间已经过去了,但互联网还是时不时地给我留下一丝“魔力”。在这种情况下,有这么好的老师是免费的。谢谢。
  • 我总是很难理解“反向控制流”这个短语。那么您能否详细说明一下上下文中的“反转”一词“您的应用程序使用相同的 I/O 服务 (2) 启动操作 (1),但控制流是反转的 ."?尽管我非常了解控制流,但我无法将“反转”一词与异步情况下发生的事情联系起来。对我来说,感觉就像选择了一个随机词来给控制流一个名称或标签。
  • inverted control flow 表示框架或库(此答案中为 Asio)回调到程序中,这些是异步操作完成时的回调。与程序直接调用框架或库的典型过程编程相比,例如:同步 send() 操作在完成时返回控制权。
  • @SamMiller “反向控制流”是asio 的术语吗?
【解决方案2】:

我想同步/异步的选择是非常特定于应用程序的。我同意异步范式可以使代码和调试更加复杂,但它确实有它的好处。

为了说明,我们从同步 IO 切换到使用异步 IO 提升 asio 的主要原因是,在我们的应用程序中阻塞 IO 不是一个选项,我们有一个多媒体流服务器,我在其中将媒体数据包流式传输到多个客户端编码后。问题是网络问题导致整个捕获-编码-交付管道被有效地停止(例如,如果与单个客户端的连接失败)。

总而言之,根据我 (ltd) 使用异步 IO 的经验,在等待 IO 完成时需要完成其他工作(例如为其他客户端提供服务等)的情况下,它可能很有用。在系统或场景中,您必须等待 IO 的结果才能继续,使用同步 IO 会简单得多。

这在双工通信系统中也很有意义(例如更复杂的协议,如 SIP、RTSP,其中客户端和服务器都可以发送请求)。自从我处理 POP 以来已经有一段时间了,但是对于您示例中的简单交换,异步 IO 可能被认为是矫枉过正。只有在确定同步 IO 不足以满足我的要求时,我才会切换到异步 IO。

WRT to boost asio 文档,我发现掌握它的最佳方法是通过示例工作。此外,您可能想要查看的链接是http://en.highscore.de/cpp/boost/index.html 它有一个关于 boost asio 的非常好的章节。 Chris Kohlhoff's (author of asio) blog 也有一些非常优秀的文章值得一看。

【讨论】:

  • 感谢您的意见。你的话对我来说是有道理的,直到现在才知道高分书。
  • @Ralf “问题是网络问题导致整个捕获-编码-交付管道被有效地停止(例如,如果与单个客户端的连接失败)。” 问题:你能解释一下the connection to a single client failednetwork issues resulted in the whole capture-encoding-deliver pipeline being effectively stalled之间的关系吗?
【解决方案3】:

同步易于控制程序流程。

异步具有更好的性能,因为它不需要为光纤任务保存/恢复寄存器。

异步 使用回调并且难以编程。我们可以尝试promise-cpp 使异步流程类似于 同步 --

http 客户端示例 --

//<1> Resolve the host
async_resolve(session->resolver_, host, port)

.then([=](tcp::resolver::results_type &results) {
    //<2> Connect to the host
    return async_connect(session->socket_, results);

}).then([=]() {
    //<3> Write the request
    return async_write(session->socket_, session->req_);

}).then([=](std::size_t bytes_transferred) {
    boost::ignore_unused(bytes_transferred);
    //<4> Read the response
    return async_read(session->socket_, session->buffer_, session->res_);

}).then([=](std::size_t bytes_transferred) {
    boost::ignore_unused(bytes_transferred);
    //<5> Write the message to standard out
    std::cout << session->res_ << std::endl;

}).then([]() {
    //<6> success, return default error_code
    return boost::system::error_code();
}, [](const boost::system::error_code err) {
    //<6> failed, return the error_code
    return err;

}).then([=](boost::system::error_code &err) {
    //<7> Gracefully close the socket
    std::cout << "shutdown..." << std::endl;
    session->socket_.shutdown(tcp::socket::shutdown_both, err);
});

Full code here

【讨论】:

  • 如何处理 .then()-chain 中的异常?你怎么能表达分支?
  • 异常被转换为 then() 中调用的第二个函数或 fail() 中调用的第一个函数的错误参数,这与 Javascript promise 中的规则类似。为了匹配不同类型的错误参数,我们可以编写多个fail()函数来捕捉不同类型,就像try/catch一样。
  • .then() 是什么
【解决方案4】:

在 Ralf 的帖子之后,我认为您需要使用 Asio 异步 API 的唯一情况是您不希望您的程序在套接字上阻塞

例如,此服务器将在 accept() 调用中阻止您的程序

asio::io_context io_context;
asio::ip::tcp::acceptor acceptor;
...
asio::ip::tcp::socket sock(io_context);
acceptor.accept(sock);

如果您的服务器程序需要做“其他事情”(例如 GUI),那么这显然是不可接受的。

另一方面,如果您的程序此时所做的只是等待传入连接,那么我认为使用异步 API 是没有意义的(不需要它,不要使用它)

我很想听听上面关于我的“唯一需要它的情况”假设的任何 cmets,因为我设计了一个包含大约 5 个不同服务器/客户端程序的大规模架构,它们在一个相当迄今为止在测试中完美运行的强大的消息数据流 (HTTP)。

所以,我想听听为什么我需要进行异步调用的论点(因为一位同事提议更改为异步)。

多线程程序也一样,利弊?

参考

What are Async Sockets?

https://www.ibm.com/docs/en/zos/2.2.0?topic=otap-clientserver-socket-programs-blocking-nonblocking-asynchronous-socket-calls

https://sudonull.com/post/121250-BoostAsio-C-Network-Programming-Chapter-5-Synchronous-vs-Asynchronous

https://www.scottklement.com/rpg/socktut/nonblocking.html

【讨论】:

    猜你喜欢
    • 2012-12-23
    • 1970-01-01
    • 2016-03-05
    • 1970-01-01
    • 1970-01-01
    • 2021-09-28
    • 1970-01-01
    • 1970-01-01
    • 2015-11-21
    相关资源
    最近更新 更多