news 2026/2/9 8:48:47

内存屏障-Volatile ,示例程序

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
内存屏障-Volatile ,示例程序

0.引用

1.进程间共享数据可见性(volatile)

考虑如下一个简单程序示例,线程A先修改 X=1,然后修改Flag=1;在线程B中使用 while(!Flag) 循环等待Flag被线程A修改为1,然后打印X的值。

int Flag 0; int X = 0; Thread A { X = 1; Flag = 1; } Thread B { while(!Flag); printf("%d\n", X); }

当前这个程序的问题是,因为编译器在编译线程B时可能会把 Flag 变量优化到寄存器里,而不是每次从内存中读取Flag的值,这样线程A对Flag值的修改在线程B中不可见。

如下一个真实的C程序,使用 "gcc -O1" 级别优化编译。

// file_name: test.c #include <stdio.h> #include <pthread.h> #include <semaphore.h> #include <stdint.h> #include <stdlib.h> #include <unistd.h> // int x = 0, y = 0; // int r1, r2; // volatile int x = 0; // volatile int y = 0; // volatile int R1, R2; // int x = 0; // int y = 0; // int R1; int Flag = 0; int X = 0; int R1 = 0; uint64_t iter_count = 0; sem_t sem_begin1; sem_t sem_begin2; sem_t sem_end; sem_t sem_lock; void *thread1(void *arg) { while(1) { sem_wait(&sem_begin1); // 1 while((rand()/(double)RAND_MAX) < 0.2); usleep(5); X = 1; Flag = 1; sem_post(&sem_end); } } void *thread2(void *arg) { while(1) { sem_wait(&sem_begin2); // 2 while((rand()/(double)RAND_MAX) < 0.2); usleep(5); while(!Flag); R1 = X; sem_post(&sem_end); } } int main() { sem_init(&sem_begin1, 0, 0); sem_init(&sem_begin2, 0, 0); sem_init(&sem_end, 0, 0); sem_init(&sem_lock, 0, 1); pthread_t t1, t2; pthread_create(&t1, NULL, thread1, NULL); pthread_create(&t2, NULL, thread2, NULL); while(1) { Flag = 0; X = 0; R1 = 0; sem_post(&sem_begin1); sem_post(&sem_begin2); sem_wait(&sem_end); sem_wait(&sem_end); iter_count++; printf("R1=%d iter_count=%lu\n", R1, iter_count); if (R1 == 0) { printf("R1=%d memory unordered iter_count = %lu\n", R1, iter_count); } } return 0; }

使用 ‘gcc -O1' 级别优化编译程序

gcc -g -O1 -o test test.c -lpthread

执行编译生成的 './test' 程序,发现'./test'执行输出5行打印之后,就不再输入打印信息。

➜ c++ ./test R1=1 iter_count=1 R1=1 iter_count=2 R1=1 iter_count=3 R1=1 iter_count=4 R1=1 iter_count=5

按照'test.c' 程序源码的设计,该程序会循环的不停打印信息。为什么现在,只打印了5行输出之后就不再执行了?

实际上是 thread2 线程进入了 'while(!x)' 死循环,使用 top 命令,和 gdb 看下 './test' 程序可以验证这一点。

使用'top'命令查看,’./test' 进程的CPU使用率是 100% 说明进程进入了死循环

使用 'gdb' 跟踪看下进入死循环的进程在执行的命令。‘ps -au' 找到 './test' 的进程号,使用 'sudo gdb -p xxx' 让 GDB attach 到 './test' 进程。

GDB 查看发现线程B就是停在了 'while(!Flag)' 中死循环。

为什么会出现这种结果,在线程A中明明已经修改了 Flag 标记值为 'Flag=1’,线程B 为什么在 'while(!Flag)' 中死循环。

线程B在'while(!Flag)'中死循环,就是说线程B取到的'Flag'变量的值一直为0。可明明线程A已经修改了内存中‘Flag’变量的值‘Flag=1',难道是线程B没有从内存中取’Flag' 变量的值么?

使用'objdump' 命令反汇编一下 './test' 程序,看下编译器编译之后再汇编程序源码层面线程B是如何获取‘Flag'便令的值的。

反汇编命令

objdump -d -S test

反汇编结果,查看反汇编之后线程B的汇编指令

void *thread2(void *arg) { 1274: f3 0f 1e fa endbr64 1278: 55 push %rbp 1279: 53 push %rbx 127a: 48 83 ec 08 sub $0x8,%rsp while(1) { sem_wait(&sem_begin2); // 2 127e: 48 8d 2d fb 2d 00 00 lea 0x2dfb(%rip),%rbp # 4080 <sem_begin2> usleep(5); while(!Flag); R1 = X; sem_post(&sem_end); 1285: 48 8d 1d d4 2d 00 00 lea 0x2dd4(%rip),%rbx # 4060 <sem_end> sem_wait(&sem_begin2); // 2 128c: 48 89 ef mov %rbp,%rdi 128f: e8 1c fe ff ff call 10b0 <sem_wait@plt> while((rand()/(double)RAND_MAX) < 0.2); 1294: e8 67 fe ff ff call 1100 <rand@plt> 1299: 66 0f ef c0 pxor %xmm0,%xmm0 129d: f2 0f 2a c0 cvtsi2sd %eax,%xmm0 12a1: f2 0f 5e 05 a7 0d 00 divsd 0xda7(%rip),%xmm0 # 2050 <_IO_stdin_used+0x50> 12a8: 00 12a9: f2 0f 10 0d a7 0d 00 movsd 0xda7(%rip),%xmm1 # 2058 <_IO_stdin_used+0x58> 12b0: 00 12b1: 66 0f 2f c8 comisd %xmm0,%xmm1 12b5: 77 dd ja 1294 <thread2+0x20> usleep(5); 12b7: bf 05 00 00 00 mov $0x5,%edi 12bc: e8 4f fe ff ff call 1110 <usleep@plt> while(!Flag); 12c1: 8b 05 09 2e 00 00 mov 0x2e09(%rip),%eax # 40d0 <Flag> 12c7: 85 c0 test %eax,%eax 12c9: 74 fc je 12c7 <thread2+0x53> R1 = X; 12cb: 8b 05 fb 2d 00 00 mov 0x2dfb(%rip),%eax # 40cc <X> 12d1: 89 05 f1 2d 00 00 mov %eax,0x2df1(%rip) # 40c8 <R1> sem_post(&sem_end); 12d7: 48 89 df mov %rbx,%rdi 12da: e8 e1 fd ff ff call 10c0 <sem_post@plt> sem_wait(&sem_begin2); // 2 12df: eb ab jmp 128c <thread2+0x18> 00000000000012e1 <main>: } }

问题点再这里,GCC 编译器使用 '-O1'级别的优化,编译器把 "Flag'变量的值优化为读取到寄存器‘%eax'里,然后一直测试 ’%eax'寄存器的值是否为 '1‘ 来执行 'while(!Flag)' 。

这样线程A对内存中"Flag"变量的值修改,线程B就一直看不到,因为线程B只从内存中读取了一次变量’Flag'的值放到 '%eax'寄存器里,之后线程B就一直使用 ‘%eax' 寄存器里的值作为 'Flag’的值。

这样线程B,就有可能在 'while(!Flag)' 中进入死循环。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/7 15:20:04

高阶函数之-数据分组的思考

看一个例子&#xff0c;刚开始&#xff0c;你可能会这样写const people [{name:ALice,age:30,sex:female},{name:BOb,age:25,sex:male},{name:Chartlie,age:30,sex:male},{name:Diana,age:25,sex:female},{name:Eva,age:25,sex:female},{name:Frank,age:25,sex:male},{name:Gr…

作者头像 李华
网站建设 2026/2/7 17:17:24

Wan2.2-T2V-5B能否理解‘慢动作’‘快进’等时间修饰词?

Wan2.2-T2V-5B能否理解“慢动作”“快进”等时间修饰词&#xff1f; 在短视频工厂每天要产出上千条内容的今天&#xff0c;AI生成视频早已不是“能不能做”的问题&#xff0c;而是“能不能秒出、批量跑、成本低”的现实拷问。&#x1f525; 于是&#xff0c;像 Wan2.2-T2V-5B …

作者头像 李华
网站建设 2026/2/9 4:59:07

终极CotEditor开源贡献完整指南:从入门到精通

终极CotEditor开源贡献完整指南&#xff1a;从入门到精通 【免费下载链接】CotEditor Lightweight Plain-Text Editor for macOS 项目地址: https://gitcode.com/gh_mirrors/co/CotEditor CotEditor作为macOS平台上一款轻量级纯文本编辑器&#xff0c;凭借其原生用户体验…

作者头像 李华
网站建设 2026/2/9 7:23:37

千万别信!留学生求职辅导真的管用吗?

千万别信&#xff01;留学生求职辅导真的管用吗&#xff1f;洞悉价值与陷阱&#xff0c;做出明智选择“求职辅导不是保险箱&#xff0c;但没有地图的航海&#xff0c;注定会触礁。”近期&#xff0c;随着秋招临近&#xff0c;围绕“留学生回国求职”的话题再次升温。一个普遍的…

作者头像 李华
网站建设 2026/2/8 0:49:08

ESD保护器件基础选型指南:从参数到实战

在电子产品的设计与生产中&#xff0c;静电放电&#xff08;ESD&#xff09;防护是确保系统可靠性的第一道防线。一颗不起眼的ESD保护二极管&#xff08;常称为ESD管、TVS管&#xff09;选型不当&#xff0c;可能导致产品在测试、生产甚至用户手中频繁失效。本文将系统梳理ESD保…

作者头像 李华
网站建设 2026/2/7 14:36:19

系统级ESD防护设计:超越单一器件的思考

为关键接口选配一颗高性能的ESD保护器件&#xff0c;只是防护设计的第一步。真正的稳健性来自于系统级的防护架构思考。本文将探讨如何将ESD保护器件融入整个PCB及系统设计&#xff0c;构建多层次、高可靠的静电防护体系。 防护等级与器件的匹配&#xff1a;并非越高越好 许…

作者头像 李华