用户级线程:一个线程阻塞,整个进程阻塞的原因

你描述的现象是用户级线程的一个显著特点和主要缺点。根本原因在于:

用户级线程的调度和管理完全由用户空间的线程库(Thread Library)完成,内核对这些用户级线程的存在是“无感知”的。对于内核来说,它只知道有一个“进程”在运行,而不知道这个进程内部有多少个用户级线程。

下面我们来详细解释这一点:

  1. 内核的视角

    • 当一个进程被调度执行时,内核调度器(操作系统的核心部分)会将CPU时间片分配给这个进程
    • 在内核看来,无论是这个进程内部有一个线程还是上百个用户级线程,它都只把它当做一个单一的执行实体。
    • 内核只会管理进程的调度、内存、I/O等资源。
  2. 线程库的作用

    • 用户级线程的创建、销毁、调度、同步等操作,都是由用户空间的线程库(比如POSIX Threads库,即Pthreads)来实现的。
    • 线程库在进程内部维护着所有用户级线程的上下文信息(如程序计数器、寄存器值、栈指针等)。
    • 当一个用户级线程执行时,它实际上是运行在分配给整个进程的CPU时间片内。线程库会根据自己的调度算法,在这些用户级线程之间进行用户态的上下文切换
  3. 阻塞式系统调用(Blocking System Calls)

    • 问题的关键在于阻塞式系统调用。当用户级线程执行一个阻塞式系统调用时(例如,read()从磁盘读取数据,write()向网络发送数据,sleep()暂停执行,或者等待某个I/O完成),它必须通过陷入内核的方式来请求操作系统服务。
    • 一旦用户级线程发出阻塞式系统调用,内核就会认为整个进程进入了阻塞状态(因为内核不知道这个进程内部还有其他用户级线程)。
    • 内核会将这个进程从运行队列中移除,放入等待队列,直到它所请求的I/O操作完成或者等待的事件发生。
  4. 结果:整个进程被挂起

    • 由于内核已经将整个进程置于阻塞状态,并停止为其分配CPU时间片,因此,即使这个进程内部还有其他用户级线程是就绪状态,它们也无法获得CPU执行,因为整个进程已经被内核“冻结”了。
    • 这意味着,当一个用户级线程因I/O或同步操作而阻塞时,整个进程都会被阻塞,导致进程内的其他用户级线程也无法执行,就好像整个进程都“停”下来了一样。

与内核级线程的对比

为了更好地理解这一点,我们可以简单对比一下内核级线程