KIOSHIROI's CS-learning Road

基于 I/O多路复用的并发编程

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

select 的几个关键点

  1. select 监控的时文件描述符集合

    在 Unix 里,socket,管道,标准输入输出,本质上都能抽象成 fd

  2. select 默认是阻塞等待

    如果没有任何 fd 就绪,他就“睡着”,有事件来了才返回。

  3. 返回后要自己判断是哪几个 fd 就绪了

  4. select 适合 fd 数量不太大时使用,因为它每次都要扫描集合,效率一般。

  • 线程并发:一个连接一个线程,各等各的。
  • I/O 多路复用:一个线程盯很多连接,谁就绪处理谁

多个都 ready 时,一个个处理

相比普通阻塞 read / accept,I/O 多路复用可以同时服务多个 I/O 对象。

进程的优劣

对于在父、子进程间共享状态信息,进程有一个非常清晰的模型:共享文件表,但是不共享用户地址空间。进程有独立的地址空间既是优点也是缺点。

优点:这样一来,一个进程不可能不小心覆盖另一个进程的虚拟内存(隔离性)。

缺点:另一方面,独立的地址空间使得进程共享状态信息变得更加困难。为了共享信息,它们必须使用显式的 IPC(进程间通信)机制,他们往往比较慢,因为进程控制和 IPC 的开销很高。

I/O 多路复用技术的优劣

I/O 多路复用可以做并发事件驱动程序的基础。在事件驱动程序中,某些事件会导致流向前推进。一般的思路是将逻辑流模型化(画)为状态机。

例:并发事件驱动 echo 服务器中逻辑流的状态机

服务器使用 I/O 多路复用,借助 select 函数检测输入事件的发生。当每个已连接描述符准备好可读时,服务器就为相应的状态机执行转移,在这里就是从描述符读和写回一个文本行。

事件驱动设计的优点:

  1. 它比基于进程的设计给了程序员更多的对程序行为的控制
    1. 基于进程的设计把请求处理交给 OS 调度器处理,用户无法控制
    2. 基于 I/O 多路复用的事件驱动设计,程序员更直接地控制 I/O 事件处理顺序、时机和粒度
  2. 一个基于 I/O 多路复用的事件驱动服务器是运行在单一进程上下文中的,因此每个逻辑流都能访问该进程的全部地址空间。这使得在流之间共享数据变得容易。且可以像对顺序程序那样用调试工具调试并发服务器
  3. 事件驱动设计常常比基于进程的设计要高效的多。因为他们不需要进程上下文切换来调度新的流。

缺点:

  1. 编码复杂。随着并发粒度的减小,复杂性还会上升。这里的粒度是指每个逻辑流每个时间片执行的指令数量。
  2. 不能充分利用多核处理器

基于线程的并发编程——基于进程和基于I/O多路复用方法的混合

线程就是运行在进程上下文的逻辑流

线程由内核自动调度。每个线程都有自己的线程上下文,包括唯一线程ID(TID),栈,栈指针,程序计数器,通用目的寄存器和条件码

所有运行在一个进程里的线程共享该进程的整个虚拟地址空间

线程执行与进程的不同处:

  1. 线程的上下文切换要比进程的上下文切换快得多

  2. 线程不像进程那样,不是按照严格的父子层次来组织的

    和一个进程相关的线程组成一个对等(线程)池,独立于其他线程创建的线程

主线程和其他线程的区别仅在于它总是进程中第一个运行的线程

对等(线程)池概念的主要影响是,一个线程可以杀死它的任何对等线程,或者等待它的任何对等线程终止。每个对等线程都能读写相同的共享数据

互斥锁加锁规则:给定所有互斥操作的一个全序,如果每个线程都是以一种顺序获得互斥锁并以相反的顺序释放,那么这个程序就是无死锁的。


在 Notion 参与讨论

本文托管在 Notion,欢迎到原文评论区留言交流

在 Notion 打开
笔记-CSAPP 第12章 并发编程
https://kioshiroi.github.io/blog/csapp12
Author KIOSHIROI
Published at 2026年5月14日
Comment seems to stuck. Try to refresh?✨