在给定线程上使用同步 I/O 提升 asio 多线程 tcp 服务器
Posted
技术标签:
【中文标题】在给定线程上使用同步 I/O 提升 asio 多线程 tcp 服务器【英文标题】:boost asio multithreaded tcp server with synchronous I/O on a given thread 【发布时间】:2010-11-30 03:47:41 【问题描述】:基本上,我想要实现的是实现一个通用的多线程 TCP 服务器,该服务器可以处理任意请求,供 2 台具有略微不同需求的不同服务器使用。
我的要求是:
-
在收到完整的初始请求之前,无法开始处理请求。 (基本上,我有一个固定大小的请求标头,其中包括整个请求的大小)。
处理请求可能会导致向请求客户端发送多个响应消息。 IE,通常,请求可以在单个响应中处理,但有时,为了响应长时间运行的数据库事务,我需要 ping 回客户端,让他们知道我仍在工作并且不会超时连接.
为了实现这一点,我一直在密切关注 boost v1.44 中的 HTTP 服务器示例 #2。通常,该示例适用于简单的情况。我注意到的是,当我扩展到同时处理多个请求时,我所做的更改以某种方式导致所有请求都由单个线程串行处理。显然,我做错了什么。
由于雇主的限制,我无法发布我正在使用的全部实际代码,但我只想说,我保留了异步调用以接受新连接,但已将异步读/写替换为同步来电。如果有你认为需要看的具体部分,我可以看看我能做什么。
基本上,我正在寻找的是如何将 boost::asio 用于多线程 TCP 服务器的指针,其中单个连接由具有同步 I/O 的单个线程处理。同样,请记住,我的抽象基于 http 服务器示例 #2(每个 CPU 一个 io_service),但我可以灵活地更改
【问题讨论】:
只是出于兴趣,您为什么要去掉 async_read/write 调用?它是异步 IO,可让您扩展......大概您已经确认io_service
确实已配置了您需要的线程数?
我总是发现 ASIO 缺乏文档。
【参考方案1】:
Boost.Asio documentation 建议每个应用程序使用一个 io_service
,并从线程池中调用 io_service::run
。
我也不清楚为什么不能使用异步 read
和 write
结合 deadline_timer
对象来定期 ping 客户端。这样的设计几乎肯定会比使用同步 reads
和 writes
的每个连接线程更好地扩展。
【讨论】:
由于 linux 具有轻量级线程,因此异步不一定比每连接线程更具可扩展性。 Windows 长期以来一直拥有轻量级线程。事实上,我已经看到基准测试表明它的可扩展性较低:mailinator.com/tymaPaulMultithreaded.pdf 旧的又是新的......【参考方案2】:一些诊断:可以在下面的代码中使用之前打印io_service_pool_.get_io_service()
的值吗?
// from server.cpp
void server::handle_accept(const boost::system::error_code& e)
if (!e)
new_connection_->start();
new_connection_.reset(new connection(
io_service_pool_.get_io_service(), request_handler_));
acceptor_.async_accept(new_connection_->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error));
在将其传递给new_connection_.reset()
之前,您需要将其存储在一个临时文件中;也就是说,不要为这个测试调用两次get_io_service()
。
我们首先必须确保您收到的是新的io_service
。
【讨论】:
我已经做到了。我对 io_service_pool 进行了检测,以吐出每个返回的 io_service 实例的地址,并且我看到地址旋转,就像我所期望的那样。问题是在调用请求处理程序时,服务器总是使用相同的线程(将 boost::current_thread::get_id() 吐出到 std::cerr)。而 request_handler 的调用是我花费大部分时间的地方...... 另外,作为参考,我在服务器的 4 核机器上,使用 8 个线程并使用分布在多台机器上的 48 个客户端线程进行测试。 阅读器中的io_service
怎么样?由于您使用的是同步调用,因此您本身没有“读取处理程序”,但在将控制权传递给请求处理程序之前,您可能在 connection::start()
中有一些其他逻辑。
我仍然不确定为什么同步调用总是转到同一个线程。我已经切换回使用异步方法,并且没有其他更改开始看到使用了单独的io_service
。在这一点上,我不知道使用io_service
池与使用线程池的单个io_service
相比是否有任何优势。我可能会切换回单一的io_service
方法。我担心的一个问题是我的第二个用例中的错误处理。我想我可以测试一下套接字是否仍然连接。【参考方案3】:
如果您正在执行大量同步 I/O,您的并发性将受限于您拥有的线程数。我建议像现在一样为所有异步 I/O(即:所有通信、计时器)设置一个 io_service,然后再决定如何处理同步 I/O。
对于同步 I/O,您需要确定您的峰值并发量。因为它是同步的并且是 I/O,所以您将需要比 CPU 更多的线程,而决定将取决于您想要多少 I/O 并发。使用单独的 io_service,然后使用 io_service::dispatch() 将工作分配到执行同步工作负载的线程中。
这样做可以避免阻塞 I/O 调用停止处理其他异步事件的问题。
【讨论】:
以上是关于在给定线程上使用同步 I/O 提升 asio 多线程 tcp 服务器的主要内容,如果未能解决你的问题,请参考以下文章