




















这是一个创建于 161 天前的主题,其中的信息可能已经有所发展或是发生改变。
gdb bt 卡在 spinlock_unlock() 位置。查了半天代码,我的每一个锁都没有问题。给我看吐血了。
最后没辙了灵机一动 gdb disassemble 发现这里死循环了:

原来是一个标志位没有设置为 volatile ,代码被优化了。大概的代码如下:
while (true) {
// 多线程中可以被修改的一个标志
if (flags_1 == STATUS_1) {
continue
}
// 其他逻辑
// ... ...
}
但是为什么不是必现的 BUG 呢? AI 给了一堆解释,但是没怎么看懂。
1 anytk 1 月 4 日跨线程的话,一般推荐用原子变量来做标志,内存序可以用 relaxed. volatile 基本留给硬件寄存器读写和 sig_atomic_t 了. |
2 echoechoin 1 月 4 日@anytk 想来也是不应该写这种死循环,AI 建议使用 refcnt 可以多个线程在 STATUS_1 状态下继续后续的代码,但是我也懒得改了。。。 |
3 aviator 1 月 4 日volatile 提供了多线程访问的可见性,是得加 |
4 qzhai 1 月 4 日分享一个死循环给我?我写死循环的经验可是多得很 |
5 PTLin 1 月 4 日简单来说就是你没用任何同步手段,例如原子变量,内存屏障,volatile 。你这种情况下有可能会遇到 相关的东西你还是去了解下内存序什么的吧。 |
6 wfg 1 月 4 日 via iPhone你贴的代码,不管有没有 continue 不都是继续循环吗? |
7 ccpp132 1 月 4 日c++的话 volatile 也不算对,应该用 atomic ,这点和 java 还不是一回事 |
8 PTLin 1 月 4 日@PTLin 甚至在一些情况下,编译器发现你后续代码没用操作这个变量,虽然你这个变量的操作是在其他线程,但是编译器并不知道这些信息,因为你没用同步手段,编译器可能会激进的吧你这个判断删除。 |
10 mmdsun 1 月 4 日 via iPhoneC 语言多线程经典 BUG 了。 让 ai 写一个 c 语言多线程自旋代码吧。不用原子锁条件变量肯定爆炸 |
12 chenyu0x00 1 月 4 日这个不是多线程的 bug ,这个是 C/C++编译器的"特性"。 while (flag == 0) { 不过如上面所说,建议多线程通信使用 atomic ,因为 atomic 还解决了多线程情况下的内存序的问题。 |
14 litchinn 1 月 4 日为什么要加 volatile 很好理解,cpu 有多级缓存,volatile 强制从内存中读取最近修改 |
16 cnbatch 1 月 4 日多线程读写的话,能用 atomic 就尽量用,C 语言也有内置的(记得是从 C11 开始的) |
19 yxd19 1 月 4 日我之前卡在 cuda device code 的内存模型上很久。。 |
21 nuk 1 月 5 日变量没有 extern ,直接传递的地址吧? gcc 检查了语法范围内没有修改的可能,直接给你优化成固定值了。用 malloc 分配 flag ,然后用指针访问就行,单读单写的情况,乱序执行和指令重排根本不会有任何 race condition 的,可以放心使用 spinlock 。 |
22 passive 1 月 5 日 via Android信息量太少,估计你这都不一定是 volatile 的问题。记得在哪里看到过类似的 warning ,找了一下原文: One notable exception is Visual Studio, where, with default settings, every volatile write has release semantics and every volatile read has acquire semantics (Microsoft Docs), and thus volatiles may be used for inter-thread synchronization. Standard volatile semantics are not applicable to multi-threaded programming, although they are sufficient for e.g. communication with a signal handler that runs in the same thread when applied to sig_atomic_t variables. The compiler option /volatile:iso can be used to restore behavior consistent with the standard, which is the default setting when the target platform is ARM. |
23 chinuno 1 月 5 日 via Android这个我以前也遇到过。特定版本 GCC 编译就会出现,换个编译器就正常。在循环里改了 flag 的值也会被优化掉,最后丢 ida 看了才发现是被编译器优化了 |
24 kaivbv 1 月 5 日这应该不是 root cause 把? |
25 qieqie 1 月 5 日这种情况用 volatile 也是错的,volatile 不保证内存序和原子性,在某些约束下(例如 x86, 单变量无数据依赖简单读写)碰巧能跑罢了。 |
26 Vaspike 1 月 5 日八股文之王, 出列: |
27 detached 1 月 5 日这就需要传教 Rust 了 :) 这里的核心问题是:编译器在做“单线程视角”的激进优化。 主线程中有一个死循环,编译器在分析上下文时发现:“在这个循环内部,没有任何指令修改了 flags_1”。既然没人改,那它就是个常量,于是编译器直接把逻辑优化成了死循环指令( jmp ),不再去内存里读值。 问题的关键在于:如何打破编译器的“单线程假设”,明确告诉它“这个变量会被其他线程修改”? 在 C 语言中:volatile 是这一场景下的补丁。它强制编译器对该变量的每次访问都必须从内存读取,禁止将其缓存到寄存器或优化掉。这确实间接实现了“告诉编译器别乱优化”的目的。 在 Rust 中: 这种 Bug 甚至无法通过编译。因为这个标识属于 Cross-thread shared state (跨线程共享状态)。Rust 的编译器(借用检查器)会强制要求你必须使用线程安全的包装类型——比如 Mutex<T> 或者 AtomicBool 。 一旦你用了这些类型,就等于在类型系统层面告诉了编译器:“这里涉及多线程并发”。编译器和底层硬件就会自动处理好优化屏障,完全不需要程序员去操心“要不要加 volatile”这种容易遗漏的细节。 |
28 mmdsun 1 月 5 日 via iPhone今天又刷到,可以看下 Linux 内核文档关于并发的内存屏障的那块。 编译器也是按标准优化,这锅甩不到编译器上面。。 |
29 franklinyu 1 月 5 日 via iPhone我记得不推荐用 volatile 做多线程同步,推荐用同步原语(比如原子变量或互斥锁) |
30 dode 1 月 6 日这不止是死循环,CPU 占用还是 100%,好排查一点 |
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。