为什么下面的代码无法输出 Hello World?
Why below code cannot output Hello World?
为什么下面的代码无法输出Hello World!与CPU缓存有关吗?不过我觉得CPU应该能保证缓存的一致性吧?应该thread_fun在thread_fun2修改值后从内存中刷新缓存。我知道 atomic 可以解决这个问题,但我不知道为什么下面的代码不起作用。
#include <stdio.h>
#include <thread>
int a = 4;
void thread_fun() {
while(a!=3) {
}
printf("Hello world!\n");
}
void thread_fun2() {
a=3;
printf("Set!\n");
}
int main() {
auto tid=std::thread(thread_fun);
auto tid2=std::thread(thread_fun2);
tid.join();
tid2.join();
}
构建选项:
g++ -o multi multi.cc -O3 -std=c++11 -lpthread
下面是 gdb 输出
(gdb) disass thread_fun
Dump of assembler code for function _Z10thread_funv:
0x0000000000400af0 <+0>: cmpl [=12=]x3,0x201599(%rip) # 0x602090 <a>
0x0000000000400af7 <+7>: je 0x400b00 <_Z10thread_funv+16>
0x0000000000400af9 <+9>: jmp 0x400af9 <_Z10thread_funv+9>
0x0000000000400afb <+11>: nopl 0x0(%rax,%rax,1)
0x0000000000400b00 <+16>: mov [=12=]x401090,%edi
0x0000000000400b05 <+21>: jmpq 0x4008f0 <puts@plt>
End of assembler dump.
(gdb) disass thread_fun2
Dump of assembler code for function _Z11thread_fun2v:
0x0000000000400b10 <+0>: mov [=12=]x40109d,%edi
0x0000000000400b15 <+5>: movl [=12=]x3,0x201571(%rip) # 0x602090 <a>
0x0000000000400b1f <+15>: jmpq 0x4008f0 <puts@plt>
End of assembler dump.
(gdb)
测试输出
[root@centos-test tmp]# ./multi
Set!
^C
[root@centos-test tmp]# ./multi
Set!
^C
[root@centos-test tmp]# ./multi
Set!
^C
[root@centos-test tmp]# ./multi
Set!
^C
[root@centos-test tmp]# ./multi
Set!
^C
更新:
谢谢大家,现在我发现这个问题实际上是由编译器引起的。
(gdb) disass thread_fun
Dump of assembler code for function _Z10thread_funv:
0x0000000000400af0 <+0>: cmpl [=14=]x3,0x201599(%rip) # 0x602090 <a>
0x0000000000400af7 <+7>: je 0x400b00 <_Z10thread_funv+16>
0x0000000000400af9 <+9>: jmp 0x400af9 <_Z10thread_funv+9> ###jump to itself
0x0000000000400afb <+11>: nopl 0x0(%rax,%rax,1)
0x0000000000400b00 <+16>: mov [=14=]x401090,%edi
0x0000000000400b05 <+21>: jmpq 0x4008f0 <puts@plt>
End of assembler dump.
编译器似乎将其视为单线程应用程序。
正式的解释是不允许read/write在多线程中访问一个非原子变量。它被称为数据竞争,它会触发未定义的行为。
因为不允许,编译器不需要将存储提交到 a
到 L1 缓存,因此它对其他线程保持不可见。当您使用 -O3
优化进行编译时,您会在代码中看到这种效果。
如您所说,解决方案是将 a
更改为 std::atomic<int>
(无数据争用类型),一切就绪。
问题是标准说允许编译器优化你的代码代码,如果它没有数据竞争(不是直接引用!)。
所以当它分析时
while(a!=3) {
}
它发现它需要检查 a!=3
并且在下次重复循环之前什么都没有发生,因此不需要再次检查 a
因为它不可能改变。
因此将 a 的类型更改为 std::atomic<int>
将强制它再次检查 a
的值并且循环应该按预期工作。
您想做的是 std::condition_variable
的典型用例
#include <stdio.h>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex m;
std::condition_variable cv;
int a = 4;
void thread_fun() {
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return a == 3;});
printf("Hello world!\n");
}
void thread_fun2() {
std::lock_guard<std::mutex> lk(cv_m);
a = 3;
printf("Set!\n");
}
int main() {
auto tid=std::thread(thread_fun);
auto tid2=std::thread(thread_fun2);
tid.join();
tid2.join();
}
注意 lock_guard 和 unique_lock 的使用有助于使用互斥锁 m 在线程 1 和线程 2 之间进行同步。
为什么下面的代码无法输出Hello World!与CPU缓存有关吗?不过我觉得CPU应该能保证缓存的一致性吧?应该thread_fun在thread_fun2修改值后从内存中刷新缓存。我知道 atomic 可以解决这个问题,但我不知道为什么下面的代码不起作用。
#include <stdio.h>
#include <thread>
int a = 4;
void thread_fun() {
while(a!=3) {
}
printf("Hello world!\n");
}
void thread_fun2() {
a=3;
printf("Set!\n");
}
int main() {
auto tid=std::thread(thread_fun);
auto tid2=std::thread(thread_fun2);
tid.join();
tid2.join();
}
构建选项:
g++ -o multi multi.cc -O3 -std=c++11 -lpthread
下面是 gdb 输出
(gdb) disass thread_fun
Dump of assembler code for function _Z10thread_funv:
0x0000000000400af0 <+0>: cmpl [=12=]x3,0x201599(%rip) # 0x602090 <a>
0x0000000000400af7 <+7>: je 0x400b00 <_Z10thread_funv+16>
0x0000000000400af9 <+9>: jmp 0x400af9 <_Z10thread_funv+9>
0x0000000000400afb <+11>: nopl 0x0(%rax,%rax,1)
0x0000000000400b00 <+16>: mov [=12=]x401090,%edi
0x0000000000400b05 <+21>: jmpq 0x4008f0 <puts@plt>
End of assembler dump.
(gdb) disass thread_fun2
Dump of assembler code for function _Z11thread_fun2v:
0x0000000000400b10 <+0>: mov [=12=]x40109d,%edi
0x0000000000400b15 <+5>: movl [=12=]x3,0x201571(%rip) # 0x602090 <a>
0x0000000000400b1f <+15>: jmpq 0x4008f0 <puts@plt>
End of assembler dump.
(gdb)
测试输出
[root@centos-test tmp]# ./multi
Set!
^C
[root@centos-test tmp]# ./multi
Set!
^C
[root@centos-test tmp]# ./multi
Set!
^C
[root@centos-test tmp]# ./multi
Set!
^C
[root@centos-test tmp]# ./multi
Set!
^C
更新: 谢谢大家,现在我发现这个问题实际上是由编译器引起的。
(gdb) disass thread_fun
Dump of assembler code for function _Z10thread_funv:
0x0000000000400af0 <+0>: cmpl [=14=]x3,0x201599(%rip) # 0x602090 <a>
0x0000000000400af7 <+7>: je 0x400b00 <_Z10thread_funv+16>
0x0000000000400af9 <+9>: jmp 0x400af9 <_Z10thread_funv+9> ###jump to itself
0x0000000000400afb <+11>: nopl 0x0(%rax,%rax,1)
0x0000000000400b00 <+16>: mov [=14=]x401090,%edi
0x0000000000400b05 <+21>: jmpq 0x4008f0 <puts@plt>
End of assembler dump.
编译器似乎将其视为单线程应用程序。
正式的解释是不允许read/write在多线程中访问一个非原子变量。它被称为数据竞争,它会触发未定义的行为。
因为不允许,编译器不需要将存储提交到 a
到 L1 缓存,因此它对其他线程保持不可见。当您使用 -O3
优化进行编译时,您会在代码中看到这种效果。
如您所说,解决方案是将 a
更改为 std::atomic<int>
(无数据争用类型),一切就绪。
问题是标准说允许编译器优化你的代码代码,如果它没有数据竞争(不是直接引用!)。
所以当它分析时
while(a!=3) {
}
它发现它需要检查 a!=3
并且在下次重复循环之前什么都没有发生,因此不需要再次检查 a
因为它不可能改变。
因此将 a 的类型更改为 std::atomic<int>
将强制它再次检查 a
的值并且循环应该按预期工作。
您想做的是 std::condition_variable
的典型用例#include <stdio.h>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex m;
std::condition_variable cv;
int a = 4;
void thread_fun() {
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return a == 3;});
printf("Hello world!\n");
}
void thread_fun2() {
std::lock_guard<std::mutex> lk(cv_m);
a = 3;
printf("Set!\n");
}
int main() {
auto tid=std::thread(thread_fun);
auto tid2=std::thread(thread_fun2);
tid.join();
tid2.join();
}
注意 lock_guard 和 unique_lock 的使用有助于使用互斥锁 m 在线程 1 和线程 2 之间进行同步。