Linux下阻塞在套接字的线程如何被操作系统重新调度?
Linux下阻塞套接字线程的唤醒与调度机制
一、线程阻塞时的状态切换
当线程调用read()、recv()这类套接字读操作,且套接字接收缓冲区没有数据时,内核会把该线程的状态从TASK_RUNNING切换为TASK_INTERRUPTIBLE(如果是不可中断的阻塞调用则为TASK_UNINTERRUPTIBLE),然后将它从CPU的运行队列中移除,调度其他就绪线程占用CPU执行。
这里要明确:不存在单独的线程专门监控FD,整个唤醒流程是内核网络栈与调度器直接协作完成的。
二、数据到达后的唤醒与调度
- 网卡收到数据后,通过硬件中断触发内核的网络中断处理程序,该程序会把数据写入对应套接字的接收缓冲区。
- 内核会检查这个套接字上是否有等待数据的阻塞线程:
- 如果有,内核会将这些线程的状态修改为
TASK_RUNNING,并把它们加入到CPU的就绪(RUNNABLE)队列中。 - 当调度器触发调度事件(比如当前线程时间片耗尽、发生中断返回用户态时),就会从就绪队列中选取合适的线程执行,被唤醒的线程也就有机会重新获得CPU资源,继续执行未完成的读写操作。
- 如果有,内核会将这些线程的状态修改为
三、FD监控的三种核心机制:select/poll/epoll
你提到的select/poll/epoll,是用户态进程主动监控多个FD事件的机制,和直接阻塞在单个套接字读写的场景不同,它们的核心差异如下:
1. select
- 工作方式:用户进程传入三个FD集合(可读、可写、异常),内核遍历所有传入的FD,检查是否有事件发生,最后返回就绪的FD数量和被修改后的FD集合。
- 局限性:默认FD数量上限为1024(可修改但代价大),每次调用都要把FD集合从用户态拷贝到内核态,且内核必须遍历所有FD,FD数量越多,效率越低。
- 适用场景:仅适合FD数量较少的简单场景。
2. poll
- 工作方式:用
pollfd结构体数组代替select的FD集合,每个结构体记录FD和要监控的事件,内核同样需要遍历所有结构体检查事件。 - 改进点:突破了select的FD数量上限,但仍然存在每次调用拷贝数据、内核遍历所有FD的开销,FD数量较多时效率依旧不高。
3. epoll
- 工作方式:采用事件驱动的模式,分为三个步骤:
- 调用
epoll_create()创建一个epoll实例,内核会为其维护一个就绪列表和红黑树(存储监控的FD及事件)。 - 通过
epoll_ctl()向实例添加、修改或删除要监控的FD及对应的事件(比如EPOLLIN表示可读)。 - 调用
epoll_wait()阻塞等待就绪事件,当有FD触发事件时,内核直接将就绪的FD列表返回给用户进程,无需遍历所有监控的FD。
- 调用
- 优势:无硬性FD数量上限(仅受系统内存限制),无需每次调用拷贝大量FD数据,内核仅返回就绪的FD,在高并发场景下效率远高于select和poll,是目前高并发网络编程的首选方案。
内容的提问来源于stack exchange,提问作者Prashant Pandey
相关产品推荐
相关产品推荐

