作者:张裕鹏 日期:2021-4-6
并发编程技术
常见的并发编程技术有 3 种:
- 进程
- I/O 多路复用
- 线程
进程
使用进程来并发编程时要注意:对于使用 fork 函数产生的子进程中,其父进程要释放对应的文件表表项,否则会出现永远不会释放的文件描述符,从而引起内存泄漏最终导致消耗光所有内存,导致系统崩溃。
I/O 多路复用
其基本思想是使用 select 函数,要求内核挂起进程,只有在一个或多个 I/O 事件发生后,才将控制返回给应用程序。
select 机制问题
- 每次调用 select,都需要把 fd_set 集合从用户态拷贝到内核态,如果 fd_set 集合很大时,那这个开销也很大;
- 同时每次调用 select 都需要在内核遍历传递进来的所有 fd_set,如果 fd_set 集合很大时,那这个开销也很大;
- 为了减少数据拷贝带来的性能损坏,内核对被监控的 fd_set 集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为 1024 );
一张图总结一下select、poll、epoll的区别:
类型 | select | poll | epoll |
---|---|---|---|
操作方式 | 遍历 | 遍历 | 回调 |
底层实现 | 数组 | 链表 | 链表 |
IO 效率 | 每次调用都进行线性遍历,时间复杂度为 O(n) | 每次调用都进行线性遍历,时间复杂度为 O(n) | 事件通知方式,每当 fd 就绪,系统注册的回调函数就会被调用,将就绪 fd 放到 readyList 中,时间复杂度为 O(n) |
最大连接数 | x86:1024 x64:2048 |
无上限 | 无上限 |
fd 拷贝 | 每次调用 select 都需要把 fd 集合从用户态拷贝到内核态 | 每次调用 poll 都需要把 fd 集合从用户态拷贝到内核态 | 调用 epoll_ctl 时拷贝进内核态保存,之后每次 epoll_wait 不拷贝 |
epoll 是 Linux 目前大规模网络并发程序开发的首选模型。在绝大多数情况下性能远超 select 和 poll。目前流行的高性能 web 服务器 Nginx 正式依赖于 epoll 提供的高效网络套接字轮询服务。但是,在并发连接不高的情况下,多线程+阻塞 I/O 方式可能性能更好。
备注
既然 select,poll,epoll 都是 I/O 多路复用的具体的实现,之所以现在同时存在,其实他们也是不同历史时期的产物
- select 出现是 1984 年在 BSD 里面实现的
- 14 年之后也就是 1997 年才实现了 poll,其实拖那么久也不是效率问题, 而是那个时代的硬件实在太弱,一台服务器处理 1 千多个链接简直就是神一样的存在了,select 很长段时间已经满足需求
- 2002, 大神 Davide Libenzi 实现了 epoll
线程
- 线程的上下文比进程要小得多(线程切换时,不需要切换:共享代码段、数据段、堆、共享库等),所以线程的上下文切换要比进程的上下文切换快得多。