在线程之间共享一个端点和接收缓冲区意味着 asio 等待处理程序在单个线程内完成
如果您的意思是“使用单线程运行服务时”,那么这是正确的。
否则,情况并非如此。相反,当您同时调用单个服务对象(即套接字,而不是 io_service)上的操作时,Asio 只是说行为是“未定义的”。
这似乎否定了允许多个线程首先调用 run 的意义。
除非处理需要相当长的时间。
Timer.5 sample 简介的第一段似乎很好地阐述了您的主题。
会话
要分离特定于请求的数据(缓冲区和端点),您需要一些会话概念。 Asio 中的一种流行机制是绑定 shared_ptrs 或 shared-from-this 会话类(boost bind 支持直接绑定到 boost::shared_ptr 实例)。
链
为避免对m_socket 成员的并发、非同步访问,您可以添加锁或使用上面链接的Timer.5 示例中记录的strand 方法。
演示
这里是 Daytime.6 异步 UDP 日间服务器,已修改为与许多服务 IO 线程一起使用。
请注意,从逻辑上讲,仍然只有一个 IO 线程(strand),因此我们不会违反套接字类记录的线程安全性。
但是,与官方示例不同,响应可能会乱序排队,具体取决于udp_session::handle_request 中实际处理所花费的时间。
注意
-
udp_session 类用于保存每个请求的缓冲区和远程端点
- 一个线程池,能够在多个内核上扩展实际处理(而不是 IO)的负载。
#include <ctime>
#include <iostream>
#include <string>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using namespace boost;
using asio::ip::udp;
using system::error_code;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
class udp_server; // forward declaration
struct udp_session : enable_shared_from_this<udp_session> {
udp_session(udp_server* server) : server_(server) {}
void handle_request(const error_code& error);
void handle_sent(const error_code& ec, std::size_t) {
// here response has been sent
if (ec) {
std::cout << "Error sending response to " << remote_endpoint_ << ": " << ec.message() << "\n";
}
}
udp::endpoint remote_endpoint_;
array<char, 100> recv_buffer_;
std::string message;
udp_server* server_;
};
class udp_server
{
typedef shared_ptr<udp_session> shared_session;
public:
udp_server(asio::io_service& io_service)
: socket_(io_service, udp::endpoint(udp::v4(), 1313)),
strand_(io_service)
{
receive_session();
}
private:
void receive_session()
{
// our session to hold the buffer + endpoint
auto session = make_shared<udp_session>(this);
socket_.async_receive_from(
asio::buffer(session->recv_buffer_),
session->remote_endpoint_,
strand_.wrap(
bind(&udp_server::handle_receive, this,
session, // keep-alive of buffer/endpoint
asio::placeholders::error,
asio::placeholders::bytes_transferred)));
}
void handle_receive(shared_session session, const error_code& ec, std::size_t /*bytes_transferred*/) {
// now, handle the current session on any available pool thread
socket_.get_io_service().post(bind(&udp_session::handle_request, session, ec));
// immediately accept new datagrams
receive_session();
}
void enqueue_response(shared_session const& session) {
socket_.async_send_to(asio::buffer(session->message), session->remote_endpoint_,
strand_.wrap(bind(&udp_session::handle_sent,
session, // keep-alive of buffer/endpoint
asio::placeholders::error,
asio::placeholders::bytes_transferred)));
}
udp::socket socket_;
asio::strand strand_;
friend struct udp_session;
};
void udp_session::handle_request(const error_code& error)
{
if (!error || error == asio::error::message_size)
{
message = make_daytime_string(); // let's assume this might be slow
// let the server coordinate actual IO
server_->enqueue_response(shared_from_this());
}
}
int main()
{
try {
asio::io_service io_service;
udp_server server(io_service);
thread_group group;
for (unsigned i = 0; i < thread::hardware_concurrency(); ++i)
group.create_thread(bind(&asio::io_service::run, ref(io_service)));
group.join_all();
}
catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
结束的想法
有趣的是,在大多数情况下,您会看到单线程版本的性能也一样,没有理由使设计复杂化。
或者,如果这确实是 CPU 密集型部分,您可以使用专用于 IO 的单线程 io_service 并使用老式工作池对请求进行后台处理。首先,这简化了设计,其次,这可能会提高 IO 任务的吞吐量,因为不再需要协调发布在链上的任务。