为什么下面的代码无法输出 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 之间进行同步。