读缓冲区(滑动窗口)耗尽与write阻塞、拆包、延迟(七)操作系统唤醒是公平的吗?
1.阻塞与唤醒机制(底层原理)
当发送缓冲区满时,内核会将该线程挂起,并将
其放入该Socket的等待队列(Wait Queue)
中,线程状态变为D(不可中断睡眠)或S。
当网卡发出数据(ACK确认)腾出空间时,网卡中断触发软中断,内核调用
sk_stream_write_space()等函数,主动调用 wake_up_interruptible()唤醒该 Socket等待队列中的线程。
2.公平性分析(严格吗?不严格)
结论是:不保证公平,只是大概率按顺序。
唤醒顺序:内核的__wake_up_common函数默认按等待队列的入队顺序(FIFO)唤醒线程,看起来是"公平排队"。
执行顺序(关键):线程被唤醒后,需要竞争Socket内核锁(lock_sock)。谁先抢到锁谁先写。由于操作系统线程调度器(CFS)不是实时调度,先唤醒的不一定先获得CPU时问片,所以严格公平性无法保证。
空间检查循环:即使被唤醒,如果剩余空间仍不够写入本次数据(例如要求写入1MB,空间只有512KB),该线程可能重新休眠,而其他线程可能刚好写入小包"插队"成功。这会导致后唤醒的线程可能写进去了,先唤醒的反而继续等。
3.特殊情况:Java NIO(非阻塞)
如果用的是Java NIO 的
SocketChannel.write(),缓冲区满时不会阻塞,而是直接返回0(写了0字节)。你需要监听OP_WRITE 事件,由Selector 在缓冲区有空闲时唤醒并通知你继续写,这个唤醒是公平的(按注册顺序),但业务队列的公平性由你的代码控制。
生产建议
如果需要严格的写入公平性(如消息队列场景),不要依赖操作系统,建议在应用层做任务队列(如 BlockingQueue) +单线程或带权重的调度 器,由业务代码保证公平分发。
浙公网安备 33010602011771号