libuv:多线程事件循环处理TCP连接的示例及进程线程选型咨询
libuv 多线程事件循环处理TCP连接及进程/线程选型问题
1. 单线程对应单事件循环的TCP处理示例
核心思路是:主线程负责监听端口并接收新连接,将TCP句柄轮询分配给预先创建的工作线程,每个工作线程运行独立的事件循环,处理对应连接的读写逻辑。以下是可运行的示例代码:
#include <uv.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define WORKER_THREAD_COUNT 4 typedef struct { uv_loop_t* loop; uv_mutex_t mutex; uv_async_t async; uv_tcp_t* client_handle; } worker_data_t; worker_data_t workers[WORKER_THREAD_COUNT]; int next_worker = 0; void on_client_read(uv_stream_t* stream, ssize_t nread, const uv_buf_t* buf) { if (nread > 0) { // 回显客户端数据 uv_write_t* req = malloc(sizeof(uv_write_t)); uv_buf_t res_buf = uv_buf_init(buf->base, nread); uv_write(req, stream, &res_buf, 1, NULL); } else if (nread < 0) { if (nread != UV_EOF) { fprintf(stderr, "读取错误: %s\n", uv_strerror(nread)); } uv_close((uv_handle_t*)stream, NULL); } if (buf->base) { free(buf->base); } } void on_client_connected(uv_stream_t* server, int status) { if (status < 0) { fprintf(stderr, "连接错误: %s\n", uv_strerror(status)); return; } uv_tcp_t* client = malloc(sizeof(uv_tcp_t)); uv_tcp_init(uv_default_loop(), client); if (uv_accept(server, (uv_stream_t*)client) == 0) { // 轮询选择工作线程 worker_data_t* worker = &workers[next_worker]; next_worker = (next_worker + 1) % WORKER_THREAD_COUNT; // 线程安全传递句柄 uv_mutex_lock(&worker->mutex); worker->client_handle = client; uv_mutex_unlock(&worker->mutex); // 通知工作线程处理新连接 uv_async_send(&worker->async); } else { uv_close((uv_handle_t*)client, NULL); free(client); } } void worker_async_cb(uv_async_t* async) { worker_data_t* data = (worker_data_t*)async->data; uv_mutex_lock(&data->mutex); uv_tcp_t* client = data->client_handle; data->client_handle = NULL; uv_mutex_unlock(&data->mutex); if (client) { // 将客户端句柄转移到工作线程的事件循环 uv_handle_set_loop((uv_handle_t*)client, data->loop); uv_read_start((uv_stream_t*)client, [](uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) { buf->base = malloc(suggested_size); buf->len = suggested_size; }, on_client_read); } } void* worker_thread(void* arg) { worker_data_t* data = (worker_data_t*)arg; data->loop = uv_loop_new(); // 初始化异步句柄,用于主线程与工作线程通信 uv_async_init(data->loop, &data->async, worker_async_cb); data->async.data = data; uv_mutex_init(&data->mutex); // 启动工作线程的事件循环 uv_run(data->loop, UV_RUN_DEFAULT); // 资源清理 uv_mutex_destroy(&data->mutex); uv_loop_delete(data->loop); return NULL; } int main() { uv_loop_t* main_loop = uv_default_loop(); uv_tcp_t server; struct sockaddr_in addr; // 创建并启动工作线程 for (int i = 0; i < WORKER_THREAD_COUNT; i++) { uv_thread_t thread; uv_thread_create(&thread, worker_thread, &workers[i]); } // 初始化监听服务器 uv_tcp_init(main_loop, &server); uv_ip4_addr("0.0.0.0", 8080, &addr); uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0); int r = uv_listen((uv_stream_t*)&server, 128, on_client_connected); if (r) { fprintf(stderr, "监听错误: %s\n", uv_strerror(r)); return 1; } return uv_run(main_loop, UV_RUN_DEFAULT); }
代码关键说明:
- 启动4个工作线程,每个线程持有独立的
uv_loop_t实例 - 主线程通过
uv_async_t向工作线程发送新连接通知,保证线程安全 - 工作线程将TCP句柄转移到自身事件循环后,启动读操作处理客户端请求
2. 多进程vs多线程的选型问题
nginx选择多进程架构,核心原因在于生产环境的稳定性需求:
- 强隔离性:单个进程崩溃不会波及其他进程,避免线程模型中因单个线程异常导致整个进程挂掉的风险;进程间内存完全独立,内存泄漏、竞态条件的影响范围被限制。
- 权限管控灵活:主进程以root权限运行(用于绑定80/443等低端口),子进程切换为普通用户权限,降低被攻击后的风险。
- 平滑升级友好:多进程架构可以通过逐步替换子进程实现热重启,不中断服务。
- Linux调度优势:Linux对进程调度的优化非常成熟,fork进程的开销在现代系统中极低,进程调度的性能和线程差异不大。
但多线程并非没有优势:
- 内存共享高效:线程共享进程内存空间,无需IPC即可传递数据,适合需要高频数据交互的场景。
- 资源开销更低:线程的创建、销毁开销远低于进程,内存占用也更少。
libuv官方示例多用多进程,本质是因为服务器场景下多进程的稳定性更符合生产标准。但如果你的场景对资源开销、通信效率要求更高,多线程架构完全可行——只要遵守libuv的跨线程句柄操作规则,做好线程安全控制即可。
内容的提问来源于stack exchange,提问作者Wad
相关产品推荐
相关产品推荐

