TCP write 阻塞与信号打断
今天工作中遇到一个有意思的事,同事调试程序发现死锁了,但是一打pstack就过去了。分析过去的堆栈,发现线程卡在 write() 上,打 pstack 后就突然继续执行了。
恰好还有一台出现问题的环境留着,等待分析验证
一 堆栈现象
1 | #0 ... write() from libpthread.so.0 |
- 卡住现象是在发送端
- 打 pstack 后线程立即恢复,行为类似“虚假唤醒”
二 原因分析
同事反馈,数据量很大。查看代码,发现是阻塞的 TCP write
那么怀疑到了发送端缓冲区:
2.1 阻塞的 TCP write
| 条件 | write 返回 | 阻塞情况 |
|---|---|---|
| send buffer 空间 ≥ size | 写入全部字节 | 不阻塞 |
| send buffer 空间 < size | 写入可用空间(短写) | 不阻塞 |
| send buffer 满 | 阻塞等待 buffer 空 | 阻塞 |
所以,如果对端不读取数据:
- 发送端 send buffer 满
- write() 阻塞,线程挂起
- 网络层也没有错误,写线程只能等待
2.2 信号打断 (EINTR)
- 阻塞的 write() 可以被 可中断信号打断:返回值:-1, errno=EINTR
- pstack attach 就是触发 signal 的一种情况,所以线程看似“自己醒了”
注意:信号打断不会解决 send buffer 满导致的阻塞,它只是让系统调用提前返回。
2.3 关于接收端
接收端是基于 libevent 的协程处理,观察堆栈没有触发事件 → 没有调用 read(),然后发送端 write() 阻塞
最终确认
杀掉接收端后,发送端 write() 立即返回。 和pstack效果一样
三 解决方式
得排查为什么接收端不收数据,而不是死锁问题。
四 总结
- TCP write 阻塞的根本原因:对端不读 → send buffer 满
- 信号(如 pstack attach)可以打断阻塞,返回 EINTR
- TCP write 短写是常态,循环处理保证数据完整
- ASan 堆栈出现只是包装函数,不是问题根源
- 安全的写函数必须循环处理短写 + EINTR,阻塞问题需用非阻塞或专门线程解决