Show HN:一个纯 C89 实现的 Go 通道,支持阻塞式 select

一个新的开源库用 C89 实现了 Go 风格的通道和阻塞式 select,引发了关于瞄准如此老旧的 C 标准究竟是可移植性的优势,还是会损害 API 质量的无谓限制的争论。评论者深入讨论了命名规范、`bool` 的使用、select 语义、分配器钩子,以及通道关闭时的检查时与使用时竞态等并发边缘情况,同时将该项目与 libmill、libdill 和 ZeroMQ 等替代方案进行比较。除技术批评外,一些参与者也反思了公共代码评审中的语气与礼仪,凸显出严谨反馈与为志愿维护者营造友好环境之间的张力。

整体反响

  • 许多评论者认为,在 C 中实现 Go 风格的通道这个想法很巧妙,也很有用,尤其适合受限或嵌入式环境。
  • 即使他们自己不会使用 C89,仍有不少人对作者的努力和对可移植性的关注表示赞赏。

C89 与更新的 C 标准

  • 一派观点认为 C89 已经过时:C11/C17(以及部分 C99)已经广泛可用,而仅限 C89 会降低 API 清晰度(例如缺少 bool)并影响代码质量。
  • 另一派则为 C89 辩护,认为它是面向旧编译器、嵌入式工具链,以及使用 TinyCC 或其他受限环境项目的实用可移植目标。
  • 还有人指出,这个库其实并不严格符合 C89(例如混合声明与语句、POSIX 线程、某些扩展),因此“纯 C89”的说法有误导性;作者后来澄清,他的意思是“可以用 -std=c89 编译”,而不是严格形式上的纯净性。

API 与设计反馈

  • 建议包括:
    • 在可能的情况下,使用类似 bool 的类型来返回状态。
    • _create/_destroy 命名来代替 _dispose
    • 避免使用 _t 后缀,因为 POSIX 对其保留(也有人认为这实际上不是问题)。
    • 支持自定义分配器、日志钩子和调试标志。
    • 暴露文件描述符,让通道可以接入现有事件循环(select/epoll/kqueue/io_uring)。
  • 有人强调,这些属于“头文件层面”的设计决策,之后若要修改会很困难,往往会破坏 ABI。

并发语义与坑点

  • 有人担心 while (!closed(chan)) { … }:如果通道在检查与操作之间关闭,就会出现经典的检查时与使用时竞态(TOCTOU)问题。
  • 与 Go 的比较:Go 的接收操作会原子地返回值以及“打开/关闭”状态;相比单独调用 closed() 探测,这种模式更受推荐。
  • 还有人问,关闭时缓冲区中的消息会怎样处理,以及向已关闭通道发送会是什么行为;评论者希望这些行为能被清楚地文档化。

与其他库和模型的比较

  • 提到的相关项目包括:libmill/libdill(结合协程与 IO 多路复用的 CSP)、ZeroMQ 的 inproc sockets、Plan 9 的 libthread、CSP 和 actor 模型。
  • 也有人澄清,这个库使用的是操作系统线程,而不是用户级协程,因此不能安全地与那些期望使用非阻塞原语的协程调度器混用。

社区与语气

  • 讨论中有相当多的元讨论围绕评审语气:有人觉得早期批评过于尖刻;也有人强调,这些技术反馈本身很有价值,只是表达时需要更谨慎一些。