Golang
主页 > 脚本 > Golang >

Linux下Epoll与Go netpoll上下文切换

2026-05-24 | 佚名 | 点击:

Go netpoll 本身不引发上下文切换

你看到的 goroutine 阻塞在 conn.Read(),不是线程(M)被挂起,而是 goroutine 被 gopark 挂起、让出 M 给其他 G 使用。此时 M 仍在运行——它立刻去执行别的 goroutine,不会触发 OS 级上下文切换。

真正触发上下文切换的,是当你手动创建大量线程(比如用 syscall.Clone)、或开了太多阻塞型系统调用(如未配对的 runtime.Entersyscall),又或者用了 CGO_ENABLED=1 且 C 函数长期阻塞。

手动调 epoll 会强制引入上下文切换

一旦绕过 runtime 直接调用 syscall.EpollWait,你就脱离了 netpoll 的调度闭环:goroutine 无法被自动 park/unpark,只能靠自己用 runtime.Entersyscall 进入系统调用态——这会让 M 脱离 P,触发一次完整的 OS 级上下文切换;等事件返回再 runtime.Exitsyscall,又要切回来。

对比 select/poll 为什么更伤上下文切换

Linux 下 select 和 poll 每次调用都要把整个 fd 集合从用户态拷贝到内核态,返回时再把就绪集合拷回;epoll 是一次性注册、增量更新,但它们共通的问题是:调用者必须自己维护事件循环线程——这个线程一旦阻塞在 select 或 poll 上,就只能干等,无法同时跑业务逻辑。

真正该盯的指标不是 epoll_wait 耗时

别花时间看 strace -e epoll_wait 的平均延迟——那只是内核通知快慢,和你的吞吐无关。netpoll 的性能瓶颈从来不在 epoll 层,而在上层是否让 goroutine 快速进出 IO 等待态。

netpoll 的设计目标就是消灭不必要的上下文切换,但它无法保护你写错的业务逻辑。goroutine 卡住的真正原因,99% 都藏在你自己写的 for 循环、锁、或没设 deadline 的 Read 里。

原文链接:
相关文章
最新更新