libuv与Boost/ASIO相比如何

How does libuv compare to Boost/ASIO?

本文关键字:ASIO Boost libuv      更新时间:2023-10-16

我对等方面感兴趣

  • 范围/功能
  • 性能
  • 到期

范围

Asio是一个C++库,最初专注于网络,但其异步I/O功能已扩展到其他资源。此外,由于Boost.Asio是Boost库的一部分,它的范围略有缩小,以防止与其他Boost库重复。例如,Boost.Asio不会提供线程抽象,因为Boost.thread已经提供了一个。

另一方面,libuv是一个C库,旨在作为Node.js的平台层。它为Windows上的IOCP、macOS上的kqueue和Linux上的epoll提供了抽象。此外,它的范围似乎略有增加,包括抽象和功能,如线程、线程池和线程间通信。

每个库的核心都提供了一个事件循环和异步I/O功能。它们在一些基本功能上有重叠,例如定时器、套接字和异步操作。libuv的范围更广,并提供了额外的功能,如线程和同步抽象、同步和异步文件系统操作、进程管理等。相比之下,Boost.Asio最初的网络重点浮出水面,因为它提供了更丰富的网络相关功能,如ICMP、SSL、同步阻塞和非阻塞操作,以及用于常见任务的更高级操作,包括从流中读取直到接收到换行符。


功能列表

以下是一些主要功能的简要并排比较。由于使用Boost.Asio的开发人员通常有其他可用的Boost库,所以我选择考虑其他Boost库(如果它们是直接提供的或实现起来很简单)。

libuv Boost事件循环:是Asio线程池:是Asio+Threads线程:线程:是线程同步:yes线程文件系统操作:同步:yes文件系统异步:是Asio+文件系统计时器:是的,阿西奥分散/聚集I/O[1]:无Asio网络:ICMP:无AsioDNS解析:仅异步AsioSSL:无AsioTCP:仅异步AsioUDP:仅异步Asio信号:处理:是Asio发送:是否IPC:UNIX域套接字:yes AsioWindows命名管道:是Asio流程管理:分离:是过程I/O管道:是进程产卵:是的过程系统查询:CPU:是否网络接口:是否串行端口:否是TTY:是不是共享库加载:yes扩展[2]

1。分散/收集I/O

2.Boost.Extension从未提交给Boost进行审查。如前所述,提交人认为它是完整的

事件循环

虽然libuv和Boost.Asio都提供了事件循环,但两者之间有一些细微的区别:

  • 虽然libuv支持多个事件循环,但它不支持从多个线程运行同一个循环。因此,在使用默认循环(uv_default_loop())时需要小心,而不是创建新循环(uv_loop_new()),因为另一个组件可能正在运行默认循环
  • Asio没有默认循环的概念;所有CCD_ 3都是它们自己的循环,允许多个线程运行。为了支持这种Boost,Asio以牺牲一些性能为代价执行内部锁定。Boost.Asio的修订历史表明,为了最大限度地减少锁定,已经进行了几次性能改进

线程池

  • libuv通过uv_queue_work提供了一个线程池。线程池大小可通过环境变量UV_THREADPOOL_SIZE进行配置。工作将在事件循环之外和线程池中执行。一旦工作完成,完成处理程序将排队在事件循环中运行
  • 虽然Boost.Asio不提供线程池,但由于io_service允许多个线程调用runio_service可以很容易地作为一个线程。这将线程管理和行为的责任交给了用户,如本例所示

线程和同步

  • libuv为线程和同步类型提供了抽象
  • 线程提供了一个线程和同步类型。这些类型中的许多都严格遵循C++11标准,但也提供了一些扩展。由于Boost.Asio允许多个线程运行单个事件循环,它提供了一种方法,可以在不使用显式锁定机制的情况下创建事件处理程序的顺序调用

文件系统操作

  • libuv为许多文件系统操作提供了抽象。每个操作有一个函数,每个操作可以是同步阻塞,也可以是异步操作。如果提供了回调,那么操作将在内部线程池中异步执行。如果没有提供回调,那么调用将被同步阻塞
  • 文件系统为许多文件系统操作提供同步阻塞调用。这些可以与Boost.Asio和线程池相结合,以创建异步文件系统操作

网络

  • libuv支持UDP和TCP套接字上的异步操作,以及DNS解析。应用程序开发人员应该注意,底层文件描述符设置为非阻塞。因此,本机同步操作应检查EAGAINEWOULDBLOCK的返回值和errno
  • Boost.Asio在网络支持方面更为丰富。此外,libuv的网络提供了许多功能,Boost.Asio支持SSL和ICMP套接字。此外,Boost.Asio除了异步操作之外,还提供同步阻塞和同步非阻塞操作。有许多独立的函数提供常见的高级操作,例如读取一定数量的字节,或者直到读取指定的分隔符

信号

  • libuv提供了抽象kill及其uv_signal_t类型和uv_signal_*操作的信号处理
  • Asio没有证明对kill的抽象,但它的signal_set提供了信号处理

IPC

  • libuv通过单个uv_pipe_t类型抽象Unix域套接字和Windows命名管道
  • Asio将两者分为local::stream_protocol::socketlocal::datagram_protocol::socketwindows::stream_handle

API差异

虽然API仅根据语言而有所不同,但这里有几个关键区别:

操作和处理程序关联

在Boost.Asio中,操作和处理程序之间存在一对一的映射。例如,每个async_write操作将调用WriteHandler一次。许多libuv操作和处理程序都是如此。但是,libuv的uv_async_send支持多对一映射。多个uv_async_send调用可能导致uv_async_cb被调用一次。

呼叫链与观察者循环

当处理任务时,例如从流/UDP读取、处理信号或等待计时器时,Boost.Asio的异步调用链更明确一些。使用libuv,可以创建一个观察程序来指定对特定事件的兴趣。然后为观察程序启动一个循环,其中提供了一个回调。在接收到感兴趣的事件后,将调用回调。另一方面,Boost.Asio要求每次应用程序对处理事件感兴趣时都要发出一个操作。

为了帮助说明这种差异,这里有一个使用Boost.Asio的异步读取循环,其中async_receive调用将被多次发出:

void start()
{
socket.async_receive( buffer, handle_read ); ----.
}                                                  |
.----------------------------------------------'
|      .---------------------------------------.
V      V                                       |
void handle_read( ... )                            |
{                                                  |
std::cout << "got data" << std::endl;            |
socket.async_receive( buffer, handle_read );   --'
}    

这里是与libuv相同的例子,每次观察者观察到套接字有数据时都会调用handle_read

uv_read_start( socket, alloc_buffer, handle_read ); --.
|
.-------------------------------------------------'
|
V
void handle_read( ... )
{
fprintf( stdout, "got datan" );
}

内存分配

由于Boost.Asio中的异步调用链和libuv中的观察者,内存分配通常发生在不同的时间。对于观察者,libuv将延迟分配,直到它接收到需要内存处理的事件之后。分配是通过用户回调完成的,在libuv内部调用,并推迟应用程序的释放责任。另一方面,许多Boost.Asio操作要求在发出异步操作之前分配内存,例如bufferasync_read的情况。Asio确实提供了null_buffers,它可以用来监听事件,允许应用程序推迟内存分配,直到需要内存为止,尽管这是不推荐的。

这种内存分配差异也出现在bind->listen->accept循环中。使用libuv,uv_listen创建一个事件循环,当连接准备好接受时,该循环将调用用户回调。这允许应用程序推迟客户端的分配,直到尝试连接为止。另一方面,Boost.Asio的io_service0只改变了acceptor的状态。async_accept侦听连接事件,并要求在调用之前分配对等方。


性能

不幸的是,我没有任何具体的基准数字来比较libuv和Boost.Asio。然而,我在实时和接近实时的应用程序中使用这些库时观察到了类似的性能。如果需要硬数字,libuv的基准测试可以作为一个起点。

此外,虽然应该进行分析以确定实际的瓶颈,但要注意内存分配。对于libuv,内存分配策略主要局限于分配器回调。另一方面,Boost.Asio的API不允许分配器回调,而是将分配策略推送到应用程序。但是,Boost.Asio中的处理程序/回调可能会被复制、分配和释放。Asio允许应用程序提供自定义内存分配函数,以便为处理程序实现内存分配策略。


到期

Boost.Asio

Asio的开发至少可以追溯到2004年10月,经过20天的同行评审,于2006年3月22日被Boost 1.35接受。它还作为TR2网络库提案的参考实现和API。Boost.Asio有相当多的文档,尽管它的有用性因用户而异。

API也有一个相当一致的感觉。此外,异步操作在操作的名称中是显式的。例如,accept是同步阻塞,而async_accept是异步的。API为常见的I/O任务提供了免费功能,例如,从流中读取直到读取rn。还注意隐藏一些特定于网络的细节,例如代表0.0.0.0的"所有接口"地址的ip::address_v4::any()

最后,Boost 1.47+提供了处理程序跟踪,这在调试时非常有用,并且支持C++11。

libuv

根据他们的github图,Node.js的开发至少可以追溯到2009年2月,libuv的开发可以追溯到2011年3月。小册子是介绍libuv的好地方。API文档在这里。

总体而言,API是相当一致和易于使用的。一个可能引起混淆的异常是uv_tcp_listen创建了一个观察者循环。这与通常具有uv_*_startuv_*_stop对函数来控制观察者循环的寿命的其他观察者不同。此外,一些uv_fs_*操作具有相当多的参数(最多7个)。同步和异步行为是根据回调(最后一个参数)的存在来确定的,同步行为的可见性可以降低。

最后,快速浏览一下libuv提交历史记录,可以发现开发人员非常活跃。

好的。我在使用这两个库方面都有一些经验,可以澄清一些事情。

首先,从概念的角度来看,这些库在设计上有很大的不同。它们有不同的体系结构,因为它们的规模不同。Boost.Asio是一个大型网络库,旨在与TCP/UDP/ICMP协议、POSIX、SSL等一起使用。Libuv只是Node.js的IOCP的跨平台抽象层,主要是。所以libuv在功能上是Boost.Asio的一个子集(通用功能只有TCP/UDP套接字线程、定时器)。在这种情况下,我们可以使用以下几个标准来比较这些库:

  1. 与Node.js-Libuv的集成要好得多,因为它是针对这个目的的(我们可以完全集成它并在各个方面使用,例如云,例如windows azure)。但Asio也实现了与Node.js事件队列驱动环境中几乎相同的功能
  2. IOCP性能-我看不出有什么不同,因为这两个库都抽象了底层的操作系统API。但他们的做法不同:Asio大量使用C++功能,如模板,有时还使用TMP。Libuv是一个原生的C库。但尽管如此,Asio对IOCP的实现是非常有效的。Asio中的UDP套接字不够好,最好使用libuv与新的C++功能集成:Asio更好(Asio 1.51广泛使用C++11异步模型、移动语义、可变模板)。就成熟度而言,Asio是一个更稳定、更成熟的项目,具有良好的文档(如果将其与libuv头描述进行比较)、互联网上的大量信息(视频对话、博客:http://www.gamedev.net/blog/950/entry-2249317-a-guide-to-getting-started-with-boostasio?pg=1等)甚至书籍(不适合专业人士,但无论如何:http://en.highscore.de/cpp/boost/index.html)。Libuv只有一本在线书籍(但也不错)http://nikhilm.github.com/uvbook/index.html还有几次视频谈话,所以很难知道所有的秘密(这个图书馆有很多秘密)。有关功能的更具体讨论,请参阅下面的评论

作为结论,我应该说,这完全取决于你的目的、你的项目以及你打算做什么

一个巨大的区别是Asio(Christopher Kohlhoff)的作者正在整理他的库,以便将其包含在C++标准库中,请参阅http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2175.pdf和http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4370.html

添加可移植性状态:截至发布此答案,根据我自己的尝试:

  • Boost.ASIO没有对iOS和Android的官方支持,例如,其构建系统不适用于iOS
  • libuv为iOS和Android轻松构建,文档中有对Android的官方支持。我自己的基于Autotools的项目的通用iOS构建脚本可以毫无问题地工作