作者:张裕鹏 日期:2021-4-6

并发编程技术

常见的并发编程技术有 3 种:

  • 进程
  • I/O 多路复用
  • 线程

进程

使用进程来并发编程时要注意:对于使用 fork 函数产生的子进程中,其父进程要释放对应的文件表表项,否则会出现永远不会释放的文件描述符,从而引起内存泄漏最终导致消耗光所有内存,导致系统崩溃。
并发编程 - 图1

I/O 多路复用

其基本思想是使用 select 函数,要求内核挂起进程,只有在一个或多个 I/O 事件发生后,才将控制返回给应用程序。

select 机制问题

  1. 每次调用 select,都需要把 fd_set 集合从用户态拷贝到内核态,如果 fd_set 集合很大时,那这个开销也很大;
  2. 同时每次调用 select 都需要在内核遍历传递进来的所有 fd_set,如果 fd_set 集合很大时,那这个开销也很大;
  3. 为了减少数据拷贝带来的性能损坏,内核对被监控的 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

线程

  • 线程的上下文比进程要小得多(线程切换时,不需要切换:共享代码段、数据段、堆、共享库等),所以线程的上下文切换要比进程的上下文切换快得多。

参考