TCP write 阻塞与信号打断

今天工作中遇到一个有意思的事,同事调试程序发现死锁了,但是一打pstack就过去了。分析过去的堆栈,发现线程卡在 write() 上,打 pstack 后就突然继续执行了。
恰好还有一台出现问题的环境留着,等待分析验证

一 堆栈现象

1
2
3
#0 ... write() from libpthread.so.0
#1 ... ?? () from libasan.so.5
#2 ... NetWrite(...) // 自己封装的写函数
  • 卡住现象是在发送端
  • 打 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效果一样

三 解决方式

得排查为什么接收端不收数据,而不是死锁问题。

四 总结

  1. TCP write 阻塞的根本原因:对端不读 → send buffer 满
  2. 信号(如 pstack attach)可以打断阻塞,返回 EINTR
  3. TCP write 短写是常态,循环处理保证数据完整
  4. ASan 堆栈出现只是包装函数,不是问题根源
  5. 安全的写函数必须循环处理短写 + EINTR,阻塞问题需用非阻塞或专门线程解决