用户级线程:一个线程阻塞,整个进程阻塞的原因
你描述的现象是用户级线程的一个显著特点和主要缺点。根本原因在于:
用户级线程的调度和管理完全由用户空间的线程库(Thread Library)完成,内核对这些用户级线程的存在是“无感知”的。对于内核来说,它只知道有一个“进程”在运行,而不知道这个进程内部有多少个用户级线程。
下面我们来详细解释这一点:
-
内核的视角:
- 当一个进程被调度执行时,内核调度器(操作系统的核心部分)会将CPU时间片分配给这个进程。
- 在内核看来,无论是这个进程内部有一个线程还是上百个用户级线程,它都只把它当做一个单一的执行实体。
- 内核只会管理进程的调度、内存、I/O等资源。
-
线程库的作用:
- 用户级线程的创建、销毁、调度、同步等操作,都是由用户空间的线程库(比如POSIX Threads库,即Pthreads)来实现的。
- 线程库在进程内部维护着所有用户级线程的上下文信息(如程序计数器、寄存器值、栈指针等)。
- 当一个用户级线程执行时,它实际上是运行在分配给整个进程的CPU时间片内。线程库会根据自己的调度算法,在这些用户级线程之间进行用户态的上下文切换。
-
阻塞式系统调用(Blocking System Calls):
- 问题的关键在于阻塞式系统调用。当用户级线程执行一个阻塞式系统调用时(例如,
read()
从磁盘读取数据,write()
向网络发送数据,sleep()
暂停执行,或者等待某个I/O完成),它必须通过陷入内核的方式来请求操作系统服务。 - 一旦用户级线程发出阻塞式系统调用,内核就会认为整个进程进入了阻塞状态(因为内核不知道这个进程内部还有其他用户级线程)。
- 内核会将这个进程从运行队列中移除,放入等待队列,直到它所请求的I/O操作完成或者等待的事件发生。
- 问题的关键在于阻塞式系统调用。当用户级线程执行一个阻塞式系统调用时(例如,
-
结果:整个进程被挂起:
- 由于内核已经将整个进程置于阻塞状态,并停止为其分配CPU时间片,因此,即使这个进程内部还有其他用户级线程是就绪状态,它们也无法获得CPU执行,因为整个进程已经被内核“冻结”了。
- 这意味着,当一个用户级线程因I/O或同步操作而阻塞时,整个进程都会被阻塞,导致进程内的其他用户级线程也无法执行,就好像整个进程都“停”下来了一样。
与内核级线程的对比
为了更好地理解这一点,我们可以简单对比一下内核级线程:
- 内核级线程(KLTs):每个线程都是内核可感知的独立调度实体。当一个内核级线程执行阻塞系统调用时,只有这一个线程会被阻塞,而同一进程内的其他内核级线程仍然可以被内核调度执行,从而实现真正的并发。