Go程序用epoll_create1必须传EPOLL_CLOEXEC(0x80000)以避免fork+exec时fd被子进程继承;添加socket到epoll时必须设置EPOLLET并配非阻塞模式;fd生命周期须由Go统一管理;epoll_wait返回0需结合errno判断是否为EINTR或超时,不可直接忽略。

epoll_create1 参数必须传 EPOLL_CLOEXEC 才安全
Go 程序用 cgo 调用 epoll_create1 时,若传 0,生成的 fd 在 fork+exec 子进程时可能被意外继承,导致子进程干扰主程序 epoll 实例。这是生产环境静默崩溃的常见源头。
正确做法是始终传 EPOLL_CLOEXEC(值为 0x80000),让内核在创建时自动设置 FD_CLOEXEC 标志:
// 正确 fd, err := unix.EpollCreate1(unix.EPOLL_CLOEXEC) // 错误(尤其在 daemon 化或 exec syscall 场景下) fd, err := unix.EpollCreate1(0)
-
unix.EpollCreate1是golang.org/x/sys/unix提供的封装,比裸调syscall.Syscall更可靠 - 不要自己硬编码
0x80000,依赖unix.EPOLL_CLOEXEC常量,避免平台差异 - 如果用
epoll_create(已废弃),必须手动fcntl(fd, F_SETFD, FD_CLOEXEC),多一步就多一个出错点
epoll_ctl 添加监听 socket 时不能漏掉 EPOLLET
默认水平触发(LT)模式在高并发下会反复通知就绪事件,导致大量无效系统调用和调度开销。Go 程序通常配合非阻塞 socket 使用边缘触发(ET),这是性能分水岭。
添加 socket 到 epoll 实例时,epoll_event.events 必须包含 EPOLLET:
立即学习“go语言免费学习笔记(深入)”;
var ev unix.EpollEvent ev.Events = unix.EPOLLIN | unix.EPOLLET // 关键:必须含 EPOLLET ev.Fd = int32(sockfd) _, err := unix.EpollCtl(epollfd, unix.EPOLL_CTL_ADD, sockfd, &ev)
- 漏掉
EPOLLET后,单个连接爆发大量请求时,epoll_wait可能连续返回同一 fd 数十次,CPU 火速拉满 - 启用
EPOLLET后,必须确保 socket 设为非阻塞(unix.SetNonblock),否则read/write会阻塞整个 goroutine -
EPOLLONESHOT可选但不推荐:它要求每次事件后手动EPOLL_CTL_MOD,在 Go 的 goroutine 模型下易引发竞态
cgo 中 socket fd 生命周期必须由 Go 控制
常见错误是让 C 代码分配 socket 并返回 fd,然后 Go 侧忘记关闭——或者更糟,在 defer unix.Close(fd) 里直接关掉被 runtime.KeepAlive 保护的 fd,导致后续 epoll_wait 返回已释放 fd 的事件。
所有 socket fd 的创建、使用、关闭必须统一走 Go 的 unix 包:
- 用
unix.Socket创建,不用socket()C 函数 - 用
unix.Bind/unix.Listen设置监听,不要混用 C 的bind/listen - 关闭时只调
unix.Close(fd),不要在 C 侧调close() - 如果必须从 C 传入 fd(如嵌入已有 C 库),立刻用
runtime.KeepAlive延长其生命周期,并在 Go 侧明确管理关闭时机
否则 runtime GC 可能在任意时刻回收 fd 对应的内存,而 epoll 实例仍持有该 fd 号,下次 epoll_wait 返回后 read 就会报 EBADF。
epoll_wait 返回事件数为 0 不代表无事发生
很多人以为 epoll_wait 返回 0 就可以跳过处理,其实这是对超时机制的误解。当传入超时值(如 -1 表示阻塞)时,返回 0 只在超时参数 >0 且未发生事件时出现;但更隐蔽的问题是信号中断(EINTR)也会导致返回 0 或负值。
必须检查 errno:
n, err := unix.EpollWait(epollfd, events, -1)
if err != nil {
if err == unix.EINTR {
continue // 被信号中断,重试
}
log.Fatal(err) // 其他错误不可忽略
}
// n == 0 仅当 timeout > 0 且无事件时成立,此时可做定时任务
- 永远不要假设
n == 0是正常空闲状态,先判err - Go runtime 本身会发信号(如
SIGURG用于 netpoll),EINTR比想象中更频繁 - 用
-1阻塞等待时,n不可能为 0;只有显式设 timeout(如10毫秒)才可能遇到
epoll 的真正复杂点不在调用接口,而在 fd 状态同步、事件消费完整性、以及和 Go runtime 信号模型的耦合——这些地方出错,不会立刻 panic,而是缓慢泄漏或间歇性丢连接。
文章来自机圈观察员网,发布者:,转载请注明出处:https://www.jqgcy.com/shoujipingce/123745.html