Nas arquiteturas de rede anteriores, o uso do Boost.Asio foi restrito a um modelo de thread única. Para maximizar a eficiência e a taxa de transferência em operações de I/O concorrentes, é necessário adotar uma abordagem multithread. O Boost.Asio suporta principalmente dois modelos de multithreading: o primeiro envolve a criação de múltiplas threads, cada uma executando seu próprio io_context independente; o segundo utiliza um único io_context compartilhado entre várias threads. Este artigo foca na implementação do primeiro modelo: um pool de serviços onde cada thread gerencia seu próprio contexto de I/O.
Características do Modelo
- Segurança de Thread no Nível de Rede: Como cada
io_contexté executado exclusivamente em sua própria thread, um socket registrado em um contexto específico terá seus callbacks invocados sempre pela mesma thread. Isso elimina problemas de concorrência na camada de I/O, garantindo que o processamento de eventos de rede seja intrinsecamente thread-safe. - Gerenciamento de Sessões e Concorrência de Negócio: O tamanho do pool é geralmente definido com base no número de núcleos da CPU. O método de aquisição retorna os contextos de forma cíclica (round-robin). Consequentemente, a primeira e a décima sétima sessão criada compartilharão o mesmo
io_contexte, portanto, a mesma thread. Se a lógica de negócios dessas sessões interagir com recursos compartilhados, a sincronização no nível da aplicação ainda será necessária. - Alta Concorrência e Desacoplamento: Diferente do modelo single-thread, onde callbacks longos bloqueiam a execução de eventos subsequentes, o modelo multithread mitiga esse gargalo. Sockets alocados em
io_contextdiferentes não interferem uns nos outros. Além disso, ao utilizar filas de lógica para desacoplar a thread de rede da thread de processamento de negócios, evitamos que operações demoradas impactem a latência de novos callbcaks.
Implementação do Pool de Contextos
Abaixo está a implementação de um pool de serviços utilizando C++20 e std::jthread para gerenciamento automático do ciclo de vida das threads.
// AsioThreadPool.hpp
#pragma once
#include <boost/asio.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <atomic>
#include <memory>
#include <thread>
#include <vector>
class AsioThreadPool {
public:
using IoContext = boost::asio::io_context;
using WorkGuard = boost::asio::executor_work_guard<IoContext::executor_type>;
using WorkGuardPtr = std::unique_ptr<WorkGuard>;
explicit AsioThreadPool(std::size_t thread_count = std::thread::hardware_concurrency());
~AsioThreadPool();
AsioThreadPool(const AsioThreadPool&) = delete;
AsioThreadPool& operator=(const AsioThreadPool&) = delete;
IoContext& AcquireContext();
void Shutdown();
private:
std::vector<std::jthread> worker_threads;
std::vector<IoContext> io_contexts;
std::vector<WorkGuardPtr> work_guards;
std::atomic<std::size_t> current_index;
};
// AsioThreadPool.cpp
#include "AsioThreadPool.hpp"
#include <iostream>
AsioThreadPool::AsioThreadPool(std::size_t thread_count)
: io_contexts(thread_count), current_index(0)
{
work_guards.reserve(thread_count);
for (std::size_t i = 0; i < thread_count; ++i) {
work_guards.push_back(std::make_unique<WorkGuard>(boost::asio::make_work_guard(io_contexts[i])));
}
for (std::size_t i = 0; i < thread_count; ++i) {
worker_threads.emplace_back([this, i]() {
io_contexts[i].run();
});
}
}
AsioThreadPool::~AsioThreadPool() {
std::cout << "AsioThreadPool destroyed." << std::endl;
}
IoContext& AsioThreadPool::AcquireContext() {
std::size_t idx = current_index.fetch_add(1, std::memory_order_relaxed);
return io_contexts[idx % io_contexts.size()];
}
void AsioThreadPool::Shutdown() {
for (auto& guard : work_guards) {
guard.reset();
}
}
Integração no Servidor Principal
No ponto de entrada da aplicação, criamos um io_context dedicado exclusivamente para aceitar novas conexões e capturar sinais do sistema operacional. Quando uma nova conexão é aceita, o contexto apropriado é retirado do pool para gerenciar a comunicação da sessão.
// main.cpp
#include "AsioThreadPool.hpp"
#include "NetworkServer.hpp"
#include <iostream>
#include <csignal>
int main() {
try {
AsioThreadPool network_pool;
boost::asio::io_context listener_context;
boost::asio::signal_set os_signals(listener_context, SIGINT, SIGTERM);
os_signals.async_wait([&](const boost::system::error_code& err, int sig) {
if (!err) {
std::cout << "Signal received, shutting down..." << std::endl;
listener_context.stop();
network_pool.Shutdown();
}
});
NetworkServer server(listener_context, network_pool, 9999);
listener_context.run();
} catch (const std::exception& ex) {
std::cerr << "Fatal error: " << ex.what() << std::endl;
}
return 0;
}