用C语言搭建多线程服务器的核心思路,就是socket编程加pthread线程库,一个连接分一个线程去处理,代码骨架不难,难在锁和资源管理。
很多新手第一次写完多线程服务器,本地测试一切正常,一旦压测就崩溃、卡死、内存暴涨,问题往往不在业务逻辑,而在线程同步、惊群效应和连接清理这些细节上,这篇文章从零开始拆解,给你一条能落地的技术路径。
Linux下最基础的多线程服务器骨架
在Linux环境下,C语言多线程服务器几乎都建立在pthread(POSIX线程)库之上,先看最核心的accept模型,这是后续所有并发形态的基础。
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
void handle_client(void arg) {
int client_fd = (int)arg;
// 这里放你的业务处理逻辑
close(client_fd);
return NULL;
}
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// bind、listen省略...
while (1) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr)&client_addr, &addr_len);
pthread_t tid;
pthread_create(&tid, NULL, handle_client, &client_fd);
pthread_detach(tid); // 关键:分离线程,让它在结束后自动回收资源
}
}
这段代码有三个值得注意的细节,第一,pthread_detach调用非常有必要,如果不分离线程,主线程又没调用pthread_join,就会造成僵尸线程,最终耗尽系统资源,第二,把client_fd的地址直接传给新线程,存在数据竞争问题,严谨写法应该先malloc一份拷贝再传入,新线程处理完再free,第三,如果并发量峰值超过数千个连接,这种一连接一线程的模型就会明显吃力,因为线程调度开销和内存占用都太大,此时需要换用线程池。
线程不安全的坑,多半出在共享数据上
行业内多数新手遇到的内存崩溃和逻辑错乱问题,根子在于多个线程同时读写同一份数据,请看下面这个典型场景。
// 全局计数器,用来统计在线连接数
int online_count = 0;
void handle_client(void arg) {
online_count++; // 这里就存在严重竞争
// 处理业务...
online_count--;
return NULL;
}
online_count++这条语句在汇编层面其实是三条指令:读取内存到寄存器、寄存器加一、写回内存,两个线程可能同时读到同一个旧值,各自加一后写回,结果只增加了1,解决方式非常明确,引入互斥锁。
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int online_count = 0;
void handle_client(void arg) {
pthread_mutex_lock(&lock);
online_count++;
pthread_mutex_unlock(&lock);
// 业务处理...
pthread_mutex_lock(&lock);
online_count--;
pthread_mutex_unlock(&lock);
return NULL;
}
尽量缩小锁的临界区范围,只保护真正的共享变量,不要把整个业务处理逻辑都塞进锁里,否则多线程就退化成了串行执行,毫无性能优势可言,如果要保护更复杂的数据结构(比如链表、哈希表),行业共识是优先考虑读写锁(pthread_rwlock_t),读操作多时能显著提升性能。
Windows下C语言多线程服务器怎么实现
在Windows下实现多线程服务器,写法与Linux有较大区别,Windows没有pthread库,推荐直接使用Windows API的CreateThread或_beginthreadex。
#include <winsock2.h>
#include <windows.h>
#include <process.h> // _beginthreadex
DWORD WINAPI handle_client(LPVOID lpParam) {
SOCKET client_sock = (SOCKET)lpParam;
// 业务处理
closesocket(client_sock);
return 0;
}
// 在main中,接收连接后创建线程
HANDLE hThread = CreateThread(NULL, 0, handle_client, (LPVOID)client_sock, 0, NULL);
CloseHandle(hThread); // 不等待线程结束,让系统自动回收句柄
如果希望代码跨平台,有两条路可走:一是用宏定义封装线程创建逻辑,根据平台条件编译;二是直接引入zthread、boost::thread等跨平台封装库,选择前一条路,能避免引入重量级依赖,对纯C项目更友好。
从单线程到多线程,性能提升的边界在哪里
很多人在本地用几台电脑测试,觉得多线程服务器提升明显,但这其中有相当大的误解,多线程提升的并不是CPU的运算速度,而是吞吐能力,也就是单位时间能处理的请求数量。
一个具体的数字对比:单线程阻塞式服务器,同一时刻只能服务一个连接,如果每个请求处理耗时20毫秒,一秒钟最多50个请求,改成多线程后,如果有4个CPU核心,理论上能同时处理4个请求,吞吐量接近200个请求每秒,但现实情况是,当线程数量超过CPU核心数的1.5到2倍时,线程上下文切换开销会抵消并发收益,性能不再线性增长,甚至下降。
所以业内专家指出,不要盲目调大线程数,连接数少但每个连接处理时间长,适合线程池加上多队列;连接数多但处理时间短,适合event loop加非阻塞I/O(比如用epoll),多线程只适合I/O密集且响应不太快(比如涉及数据库查询、文件读写)的业务场景。
C语言多线程服务器高并发怎么优化
限制线程数量,使用信号量
一个最简单的限流手段,是定义一个全局信号量,初始值为最大线程数,每次创建线程前先sem_wait,线程退出前sem_post,这样当线程数达到上限,主线程会阻塞在sem_wait上,不再accept新连接,达到限流目的。
解决惊群效应
在内核版本较老的Linux系统上,多个线程同时阻塞在accept上,一个新连接到来会唤醒所有线程,但只有一个线程能成功accept,这就是经典的惊群问题,虽然较新内核中accept已经不再惊群,但epoll_wait依然存在类似问题,解决方法是让主线程独自调用accept,然后把客户端套接字分发给工作线程处理,避免所有线程都监听同一套接字。
设置套接字选项
在服务端调用listen之前,设置两个关键的套接字选项,能避免大量性能和安全问题。
SO_REUSEADDR:允许端口快速重用,否则服务器重启时经常报”Address already in use”。TCP_NODELAY:对每个客户端套接字设置,禁用Nagle算法,减少小数据包的延迟。
多数实际部署的多线程服务器,在网络层面上还需要对accept返回的每个连接设置非阻塞模式,配合poll或epoll使用,避免单个客户端发送慢拖累整个线程。
大流量场景下用线程池
每来一个连接就创建线程,在高并发下线程的新建和销毁开销非常大,线程池的思路是提前创建固定数量的工作线程,主线程收到新连接后,把任务塞进任务队列,工作线程从队列里取出任务来执行。
一个简单的线程池结构包含:
- 任务链表,每个节点存有函数指针和参数。
- 互斥锁加条件变量,工作线程在没有任务时休眠在条件变量上。
- 主线程插入任务后,通知一个工作线程去取。
线程池的大小通常根据CPU核心数乘以2再加1来估算,这只是经验值,实际吞吐量需要通过压测工具(如ab或wrk)校准。
Q&A:C语言多线程服务器常见问题选答
如何处理多个客户端同时关闭连接的情况?
当某个客户端正常关闭,服务端的recv或read函数会返回0,如果返回-1且errno等于ECONNRESET,说明连接被对端异常重置,无论哪种情况,直接关闭对应的套接字,并释放与这个连接关联的内存资源即可,如果线程在分享全局链表,操作链表前后必须加锁。
编写多线程服务器时,Linux和Windows下最明显的语法差异是什么?
Linux下使用pthread_create和pthread_join,线程函数签名是void ()(void),Windows下使用CreateThread或_beginthreadex,线程函数签名是DWORD WINAPI ()(LPVOID),且返回值类型不同,线程句柄需要调用CloseHandle关闭,如果要做跨平台,用条件编译封装一个统一接口函数。
多线程服务器遇到内存泄漏应该从哪个方向排查?
先使用valgrind工具跑一遍数据集较小的测试用例,重点检查所有在连接线程内malloc的内存块是否都有对应的free,如果每次连接泄漏几十字节,通常在内存高负载时才有明显表现,可用top命令观察进程的RES内存在压测过程中是否持续上涨,另一个常见的泄漏来源是pthread_create被无限调用而没有设置detach,线程栈空间无法回收。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/794514.html


![[C语言]多线程程序入门教程](https://i2.hdslb.com/bfs/archive/a6d09ec47d90732e078664cb3afb247507dfcccf.jpg)