vibe coding + gcc bug 导致的线程池死锁问题
个月前我让 AI 帮我写了一个线程池GitHub Repo。不得不说就最终结果而言确实惊艳和 github 上几个同类线程池项目相比在多个评估维度上明显领先[1]。但就中间过程而言也并不全是“眩晕瘫坐就像看到原子弹爆炸”的既视感在个别环节上AI 也会犯错甚至不知道错在了哪里。故事哦不事故是这样的。话说那还是本人没有广泛使用 Agent 的落后时代也是可以在 arena.ai 上与 claude opus 4.6 无限对话的美好时代。有一天我突发奇想让 opus 4.6 帮我实现一个 C 线程池于是它“背”出了那个“C11 100 行实现线程池”的经典代码progschj/ThreadPool[2]。我自然是不满意的于是让 opus 4.6 分析当前实现有否有性能优化的空间。“啪”地一下很快啊它指出当前实现采用“单一队列 mutex cv”的模式高并发下会存在激烈的锁争用和严重的系统调用开销并提出使用“无锁队列 任务窃取”的优化方案。我一看哎呦不错哦虽说是线程池优化的基操但 AI 能很快地给出来说明基本的推理能力以及知识的广度还是在线的。那还等什么麻溜的开干用 C23测试用例也安排上于是又是“啪”地一下很快啊代码都吐出来了。我先在 Windows 试了一下代码无需任何修改直接就能跑丝滑真丝滑厉害真厉害完啦感觉明天我就要被淘汰了激动的心颤抖的手我点开了虚拟机想在 Linux 上再感受一波 AI 的暴击。然而不出意外地出意外了——直接卡死[1] 仅基于我的 benchmark不代表 AI 版本优于所有同类项目也不代表在所有方面“完胜”参与对比的其它项目。[2] 不光是 opus 4.6其它 AI 也是如此。2 死锁现场那时的我还没有用上 Agent也还没有懒到“帮我解决这个bug”的地步。于是我 gdb attach 上去很快获得了现场。下面我将提供此次事故的源码、环境、dgb 信息。2.1 源码点击展开 thread_pool.h点击展开 main.cpp2.2 环境信息OSUbuntu 虚拟机$ uname -aLinux user1 5.15.0-171-generic #181-Ubuntu SMP Fri Feb 6 22:44:50 UTC 2026 x86_64 x86_64 x86_64 GNU/Linuxg 版本$ g --versiong (Ubuntu 15.2.0-15ubuntu122ppa2) 15.2.0Copyright © 2025 Free Software Foundation, Inc.This is free software; see the source for copying conditions. There is NOwarranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.编译命令g -stdc23 -O0 -g -fno-omit-frame-pointer -o bench main.cpp执行命令./benchstd::thread::hardware_concurrency()输出22.3 gdb 调试信息点击展开 gdb 调试信息3 诊断过程很明显一个 worker 线程卡在了 sem_.acquire()导致主线程也卡在了析构函数的 std::thread::join()。但 worker 线程为什么会卡住我却不知道了。那问 AI 呗我把栈都抓出来了剩下的交给 AI还不是手拿把掐然而问题喂给 opus 4.6 后它开始疯狂思考“等等我再看一遍”“让我再检查 xxx”……直到平台返回错误码。我猜测是思考太多触发了 claude 或者 arena.ai 的限制我又把同样的问题丢给 GPT 和 Gemini它们倒是给出了答案但一试全都不对。最后您猜怎么着谁解决了这个问题是 Grok!惊不惊喜意不意外“啪”地一下Grok 告诉我代码没问题是 libstdc std::counting_semaphore::acquire() 的已知 bugGCC PR104928眩晕瘫坐原子弹爆炸作为事后诸葛亮我忽然明白了为什么 opus 4.6 “卡死”了因为它和我一样压根没往“gcc 自身 bug”方面去想反而在一遍遍疯狂审查一份压根就不存在逻辑问题的代码为什么 Grok 做对了它是这样想的代码逻辑没有问题sem_.M_counter 52993 (非 0 值)但 sem.acquire()却陷入了 wait这不正常我去找找是否有 std::counting_semaphore::acquire()的已知 bug。找到了和当前问题对得上就是它现在回过头来想想这不就是排查这类问提的正常套路吗只要注意到了第 2 步的异常剩下的不是顺理成章水到渠成吗很可惜我没有注意到更没敢怀疑是 GCC 自己的问题。我真傻真的。一个 AI 有一个 AI 的长处联网搜索这一块不得不说Grok 还是能打的。4 PR104928 bug 分析4.1 背景知识为帮助读者理解后文的 bug 分析这里简单介绍必要知识。std::counting_semaphore配合其 release()和 acquire()方法可以实现一种事件通知机制。release():逻辑上相当于“发放通行证”只有获得通行证的线程才可以做某种动作比如访问共享资源。底层实现上会对计数器 _M_counter原子加 1表示“发放 1 张通行证”。同时如果 _M_counter加 1 前的值是 0意味着可能有其它线程正在等待通行证陷入了睡眠因此会执行 notify 操作以唤醒正在等待的线程。acquire():逻辑上相当于“获取通行证”。底层实现上会通过 CAS 操作对计数器 _M_counter原子减 1表示“抢占 1 张通行证”。如果 CAS 操作之前 _M_counter 0表明“没有可用通行证”线程就会 wait()等待生产者发放通行证时被唤醒。换言之在没有 bug 的前提下若线程在调用 acquire()时陷入睡眠必然有 _M_counter 0。以上只是 std::counting_semaphore的冰山一角其它内容因与本文主题无关故不做介绍感兴趣的读者请自行学习。4.2 修复前代码acquire()的底层实现是 _M_acquire()。_GLIBCXX_ALWAYS_INLINE void_M_acquire() noexcept{auto const __vfn [this]{ return _S_get_current(this-_M_counter); };auto const __pred [this](__count_type __cur) {return _S_do_try_acquire(this-_M_counter, __cur); // 关键predicate 里做 CAS};std::__atomic_wait_address(_M_counter, __pred, __vfn, true); // 直接等待}_S_do_try_acquire的实现是static _GLIBCXX_ALWAYS_INLINE bool_S_do_try_acquire(__count_type* __counter, __count_type __old) noexcept{if (__old 0)return false;return __atomic_impl::compare_exchange_strong( // CAS__counter, __old, __old - 1,memory_order::acquire, memory_order::relaxed);}不难看出_M_acquire()的核心就是执行 std::__atomic_wait_address根据源码注释的描述如果 __pred(__vfn)的结果是 false那么 std::__atomic_wait_address就会 wait在 _M_counter这个地址上。__vfn就是用来加载 _M_counter的。__pred是一个基于 _M_counter做判断的谓词Predicate如果 _M_counter的旧值__vfn读到的那个已经是 0 了不能再减直接返回 false。否则通过 CAS 操作__atomic_impl::compare_exchange_strong尝试将 _M_counter减 1返回 CAS 结果如果减 1 成功返回 true否则返回 false。std::__atomic_wait_address根据 __pred返回结果决定是否 wait。4.3 bug 触发根因bug 出在 __pred的实现上。作为一个 Predicate__pred理论上应该是一个 Pure Function并且应该是 No Side Effects 的即除了返回 true/false外它不应该修改输入或者全局状态。但这里GCC 犯了一个教科书级别的错误在 __pred中使用 CAS 修改计数器 _M_counter过程在高并发场景下假设线程 A 在执行 CAS 操作前的一瞬间另一个线程改了M_counter的值生产者线程执行了 sem.release()或者其它执行 sem_.acquire()的 worker 线程 CAS 成功导致线程 A 的 CAS 失败。于是false沿 std::__atomic_compare_exchange_strong -- _S_do_try_acquire -- __pred一路返回给 std::__atomic_wait_address。线程 A 陷入睡眠。如果此后再没有线程触发 notify线程 A 将永久睡死4.4 bug 修复对应 commit修复后代码void _M_acquire() noexcept{auto const __vfn [this]{ return _S_get_current(this-_M_counter); };auto __val __vfn();// 注意这里按引用捕获 __valauto const __pred [__val](__count_type __cur) {if (__cur 0){__val __cur; // 一个很有意思的细节后面解释return true;}return false;};while (!_S_do_try_acquire(_M_counter, __val))if (__val 0)std::__atomic_wait_address(_M_counter, __pred, __vfn, true);}// 另外的修改是_S_do_try_acquire 的第二个参数 __old 由传值改为传引用static _GLIBCXX_ALWAYS_INLINE bool_S_do_try_acquire(__count_type* __counter, __count_type __old) noexcept{ /* … */}对于不想深究细节的读者只需明白核心修复就是让 __pred恢复一个 Predicate 该有的样子把 CAS 操作移出去。注__val是局部变量__val __cur不违背“不应该修改输入或者全局状态”的约束。想要深入了解的读者请接着往下看。要更好地理解这个修复需要了解两点信息抛开 bug 不谈按照设计预期只要调用了 std::counting_semaphore::acquire()就一定会对计数器 _M_counter减 1要么 _M_counter大于 0 时 CAS 成功已减 1acquire()直接返回。要么发现 _M_counter等于 0睡眠等待被唤醒后再减 1。std::__atomic_wait_address中线程被唤醒后还会再调用 __pred(__vfn())逻辑上是这样实际代码不是这么写的详见源码。修复前的逻辑没意识到 bug 的视角如果 __pred返回 true说明 CAS 中减 1 成功_M_acquire()直接返回。如果 __pred返回 false说明 _M_counter为 0陷入睡眠。命中 bug: 写这份代码的人没有意识到并发竞争可能导致 CAS 失败返回 false但此时 _M_counter 0在 std::__atomic_wait_address内部线程被唤醒后会再次执行 __pred此时会再次通过 CAS 做减 1 操作。若 _pred返回 false就继续睡否则acquire()结束从用户视角看线程真的被唤醒。修复后的逻辑先执行 while循环中的条件 _S_do_try_acquire注意两个关键事实它们保证了 _S_do_try_acquire函数退出后__val一定保存了 _M_counter的最新值。_S_do_try_acquire中__val按引用传递。对于 __atomic_impl::compare_exchange_strong(__counter, __old, __old - 1, …)如果 CAS 失败__old会被更新为 __counter指向的内存即 _M_countdr的最新值这是 C 下 CAS 操作的一个特性。如果 _S_do_try_acquire返回 true说明通过 CAS 减 1成功while (!_S_do_try_acquire(_M_counter, __val))不命中_M_acquire()直接结束。否则进入到 while循环的内部。如前所述此时 __val保存了 _M_counter的最新值。如果 _val 0说明 _M_counter可能一开始就是 0根本没进入 CAS或者别的线程 CAS 成功将其由 1 改成了 0当前线程 CAS 失败。但不管哪种情况当前线程不得不进入睡眠通行证为 0啥也干不了。当线程被唤醒会再次进入 while循环再次执行 _S_do_try_acquire如果 _S_do_try_acquire返回 true说明减 1 成功_M_acquire()直接结束否则接着进入 if (__val 0)的逻辑继续睡眠……否则说明当前线程在 CAS 竞争中失败了不然 _S_do_try_acquire不会返回 false被别人抢先拿走了通行证但剩余通行证数量不为0于是再次进入 while循环继续争抢下一张通行证。一个细节修改后的代码lambda表达式 __pred按引用捕获了 __val并且当 __cur即 _M_counter的最新值大于 0 时将其赋值给 __val。这有什么作用呢前面说过在 std::__atomic_wait_address中当线程被唤醒时会再次执行调用 __pred(__vfn())。在 __pred内部将 _M_counter最新值赋值给 __val意味着当线程从 std::__atomic_wait_address中退出回到 while (!_S_do_try_acquire(_M_counter, __val))时__val的值就是 _M_counter的最新值这省去了一次 atomic load是一个性能优化。否则代码就需要这样写void _M_acquire() noexcept{// 其它保持不变…// 如果 __pred 内部不执行 __var __curauto const __pred [](__count_type __cur) {if (__cur 0) {return true;}return false;};while (!_S_do_try_acquire(_M_counter, __val))if (__val 0) {std::__atomic_wait_address(_M_counter, __pred, __vfn, true);__val __vfn(); // 那么这里就必须重新加载 _M_counter}}5 线程池死锁分析5.1 Ubantu libstdc 代码段我的代码是在 Ubantu 系统上构建的与 PR104928 在细节上有些不一样。下面我将提供 Ubantu 上 libstdc 相关代码段这些代码与 gdb 调试信息中引用的代码完全一致。点击展开 semaphore_base.h 中的代码段点击展开 atomic_wait.h 中的代码段5.2 关键信息结合上述代码段以及 gdb 打印的栈帧可以发现以下关键信息信息1worker 线程等在哪里worker 线程 #1 栈帧#1 0x0000558ba2cb9fc9 in std::__detail::__platform_wait (__addr0x7ffee66d4a00, __val1) at /usr/include/C/15/bits/atomic_wait.h:114sem_地址(gdb) p sem_$1 (std::counting_semaphore2147483647 *) 0x7ffee66d4a00结合 std::__atomic_wait_address_bare源码不难看出死锁发生时worker 线程卡在 _platform_wait(sem._M_counter, 1)上。信息2 sem_计数值当形成死锁局面worker 线程在 wait()主线程在 join()时sem_._M_counter值为 52993。(gdb) p sem_

相关新闻

2026 年十五类新型网络威胁演化机理与分层防御体系实证研究

2026 年十五类新型网络威胁演化机理与分层防御体系实证研究

摘要 数字经济全域云化、人工智能产业化、物联网规模化、远程办公常态化持续拓宽网络攻击面,网络威胁呈现自动化、产业化、AI 赋能、跨链路传导的全新特征。本文以 CloudSEK 发布的《2026 年塑造网络安全格局的 15 项新型网络威胁趋势》原始行业报告为核心研究素材&…

2026/7/22 19:27:02 阅读更多 →
计算机毕业设计之基于springboot的食堂管理系统

计算机毕业设计之基于springboot的食堂管理系统

摘要互联网的普及为人们的日常生活提供了极大的方便。因此,将目前的网上注册登记与网上进行整合,采用springboot框架搭建了网上食堂管理系统平台,从而达到了食堂的信息化管理。网络平台的运用使得食堂管理系统体系能被大范围、深入地推广&…

2026/7/22 19:27:02 阅读更多 →
AI搜索工具选型进入倒计时:2024Q3起主流厂商将停更非向量原生架构,现在决策=节省200万迁移成本

AI搜索工具选型进入倒计时:2024Q3起主流厂商将停更非向量原生架构,现在决策=节省200万迁移成本

更多请点击: https://kaifayun.com 第一章:AI搜索工具选型进入倒计时:2024Q3起主流厂商将停更非向量原生架构,现在决策节省200万迁移成本 2024年第三季度起,Elastic、Algolia、Sinequa等头部搜索平台将正式终止对传统…

2026/7/22 19:27:02 阅读更多 →

最新新闻

计算机毕业设计之基于SpringBoot的社区停车场管理系统的设计与实现

计算机毕业设计之基于SpringBoot的社区停车场管理系统的设计与实现

本文着重研究了一个基于springboot 的社区停车场管理系统的开发与实践。文中首先探讨了社区停车场管理的关键作用及其在可持续发展方面的重要性,进而论述了采用 VUE框架进行平台开发的理由。文章接着详细介绍了 JAVA语言的优势并讨论了为什么选择 springboot框架来支…

2026/7/22 20:06:24 阅读更多 →
移动Web开发核心技术:响应式布局与性能优化

移动Web开发核心技术:响应式布局与性能优化

1. 移动Web开发的核心挑战与应对策略移动Web开发与传统PC端开发存在显著差异,这些差异主要源于移动设备的三个特性:屏幕尺寸限制、触控交互方式以及网络环境的不稳定性。作为从业十年的前端开发者,我总结了移动端特有的技术痛点及解决方案。1…

2026/7/22 20:06:24 阅读更多 →
如何使用Jellium Desktop进行音频环绕声测试:完整设置指南

如何使用Jellium Desktop进行音频环绕声测试:完整设置指南

如何使用Jellium Desktop进行音频环绕声测试:完整设置指南 【免费下载链接】jellium-desktop An unofficial desktop client for Jellyfin 项目地址: https://gitcode.com/GitHub_Trending/je/jellium-desktop Jellium Desktop是一款非官方的Jellyfin桌面客户…

2026/7/22 20:06:24 阅读更多 →
打造完美Kubernetes监控面板:Kube Eagle + Grafana实战教程

打造完美Kubernetes监控面板:Kube Eagle + Grafana实战教程

打造完美Kubernetes监控面板:Kube Eagle Grafana实战教程 【免费下载链接】kube-eagle A prometheus exporter created to provide a better overview of your resource allocation and utilization in a Kubernetes cluster. 项目地址: https://gitcode.com/gh_…

2026/7/22 20:05:24 阅读更多 →
gym-trading策略开发实战:使用TensorFlow实现策略梯度算法

gym-trading策略开发实战:使用TensorFlow实现策略梯度算法

gym-trading策略开发实战:使用TensorFlow实现策略梯度算法 【免费下载链接】gym-trading Environment for reinforcement-learning algorithmic trading models 项目地址: https://gitcode.com/gh_mirrors/gy/gym-trading gym-trading是一个专为强化学习算法…

2026/7/22 20:05:24 阅读更多 →
Markdown 完整权威语法手册(超详细版)

Markdown 完整权威语法手册(超详细版)

前言 Markdown 是轻量级标记语言,易读易写,兼容绝大多数平台:GitHub、GitLab、Typora、VSCode、掘金、语雀、Notion、VuePress、Hexo 等。 分为标准基础语法(通用全平台兼容) 扩展 GFM 语法(GitHub Flavo…

2026/7/22 20:04:23 阅读更多 →

日新闻

TI DSP系统配置模块SYSCFG详解:中断机制与主设备优先级配置实战

TI DSP系统配置模块SYSCFG详解:中断机制与主设备优先级配置实战

1. 项目概述与SYSCFG模块的核心价值在嵌入式系统,尤其是像TI C6000系列这样的高性能DSP开发中,我们常常会与芯片手册里那些密密麻麻的寄存器打交道。很多开发者可能更关注算法实现、内存优化或者外设驱动,但对于一个稳定、高效的系统而言&…

2026/7/22 0:00:26 阅读更多 →
微信Server酱:高到达率的应急通知方案实践

微信Server酱:高到达率的应急通知方案实践

1. 为什么我们需要"最次"的通知方案? 在数字化协作环境中,消息通知系统的重要性不言而喻明。但现实情况是,企业级通知方案往往需要复杂的API对接(如企业微信、钉钉、飞书),个人开发者的小项目又经…

2026/7/22 0:00:26 阅读更多 →
甲方要的“简洁“PPT,到底是简洁还是省事?

甲方要的“简洁“PPT,到底是简洁还是省事?

甲方说"简洁一点",乙方听到的是"少做几页"。甲方说"不要太复杂",乙方理解成"别放图表了"。结果交过去,甲方说"我说的简洁不是这个意思"。"简洁"这个词在PPT语境里,是…

2026/7/22 0:00:26 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/22 8:58:19 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/22 19:43:43 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/22 12:54:44 阅读更多 →

月新闻