【DeepSeek】经典的多线程并发服务器模型
·
通过引入多线程,我们完美解决了刚才提到的“独占服务器”问题,实现了既能长连接(不 close),又不耽误接待新客户。
1. 这个模型的逻辑是这样的:
- 主线程:只负责做“迎宾”工作。它在
while循环里不断accept。 - 子线程:一旦
accept返回一个client_fd,主线程立马把这个fd扔给一个新创建的子线程,然后主线程转头继续去accept下一个。 - 各忙各的:子线程自己在那个函数里跑
while(recv/send),不管聊多久,都不会卡住主线程。
2. 代码结构示意
// 定义子线程要执行的函数
void *handle_client(void *arg) {
int client_fd = *(int*)arg;
// 子线程:死磕这个客户,长连接模式
while (1) {
int ret = recv(client_fd, buffer, ...);
if (ret <= 0) break; // 客户走了或出错了
// 处理业务
send(client_fd, response, ...);
}
// 客户走了,子线程负责关闭 fd 并退出
close(client_fd);
return NULL;
}
int main() {
// 初始化 socket, bind, listen...
while (1) {
int client_fd = accept(server_fd, ...);
// 创建新线程,把 client_fd 传进去
pthread_t tid;
pthread_create(&tid, NULL, handle_client, &client_fd);
// 主线程不管了,立刻回到循环开头接待下一个
}
}
3. 虽然可行,但有三个“坑”要注意
虽然“起一个线程”听起来很简单,但在实际的高并发服务器开发中,直接这样写会有隐患:
A. 资源消耗(C10K 问题)
如果你的服务器只有几十、几百个连接,这个方法非常完美。
但如果有一万个客户端同时连上来(C10K 问题):
- 内存爆炸:每个线程默认栈空间可能是 8MB 或 10MB。10000 个线程 ≈\approx≈ 10GB 内存,服务器直接撑爆。
- CPU 抖颤:CPU 在上万个线程之间切换,系统会变得非常慢。
B. 线程安全(竞态条件)
如果多个线程(对应多个客户)同时操作同一个资源(比如全局变量、<u>同一个文件</u>、或者数据库连接池),你需要加锁,否则数据会乱套。
C. 文件描述符传递的坑
在上面的伪代码中,pthread_create 传的是 &client_fd 的地址。这是一个非常经典的 Bug。
- 问题:主线程循环很快,可能在子线程读取
client_fd之前,主线程已经修改了client_fd(指向了下一个客户)。 - 解决:应该动态
malloc一块内存存client_fd,传给子线程,或者直接传值(如果指针大小能存下 int)。
4. 更好的替代方案
为了解决“线程太多资源不够”的问题,现代高性能服务器通常采用以下两种方案之一:
-
线程池:
- 不要来一个客户就起一个线程。
- 预先创建好比如 4 个或 8 个线程。
- 把
client_fd放进一个任务队列里,空闲的线程自己去队列里取任务处理。
-
IO 多路复用:
- 一个线程就能管理上万个连接。
- 它不阻塞在
recv上,而是阻塞在select/epoll上。哪个fd有数据来了,就去处理哪个。
总结
- 你的想法是对的:多线程确实解决了“不 close 导致无法 accept”的矛盾。
- 适用场景:适合连接数不多、逻辑比较复杂的场景。
- 进阶思考:如果连接数成千上万,记得用线程池或epoll来优化。
更多推荐



所有评论(0)