Почему приведенный ниже код не может выводить Hello World? - PullRequest
0 голосов
/ 07 декабря 2018

Почему приведенный ниже код не может вывести Hello World!Это связано с кешем процессора?Но я думаю, что процессор должен гарантировать целостность кэша, верно?Если 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   $0x3,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    $0x401090,%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    $0x40109d,%edi
   0x0000000000400b15 <+5>:     movl   $0x3,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   $0x3,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    $0x401090,%edi
   0x0000000000400b05 <+21>:    jmpq   0x4008f0 <puts@plt>
End of assembler dump.

Кажется, компилятор рассматривал его какоднопоточное приложение.

Ответы [ 3 ]

0 голосов
/ 07 декабря 2018

Проблема в том, что стандарт гласит, что компиляторам разрешено оптимизировать код вашего кода AS-IF, если бы он был свободен от гонки данных (не прямой цитатой!).

Поэтому, когда он анализирует

while(a!=3) {

}

Он видит, что нужно проверить a!=3, и ничего не происходит, пока в следующий раз цикл не повторится, следовательно, нет необходимости проверять a снова, так как он не мог измениться.

Таким образом, изменениетип от a до std::atomic<int> заставит его снова проверить значение a, и цикл должен работать, как задумано.

0 голосов
/ 07 декабря 2018

То, что вы хотите сделать, это типичный вариант использования для 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 помогает синхронизировать между thread1 и thread2 с помощью мьютексам.

0 голосов
/ 07 декабря 2018

Формальное объяснение состоит в том, что вам не разрешен доступ на чтение / запись к неатомарным переменным в нескольких потоках.Это называется гонкой данных и запускает неопределенное поведение.

Поскольку это не разрешено, компилятору не требуется фиксировать хранилище в a к L1-кешу, и поэтому оно остается невидимым для других потоков.Вы видите эффект этого в своем коде, когда вы компилируете с оптимизацией -O3.

Как вы сказали, решение состоит в том, чтобы изменить a на std::atomic<int> (тип без данных), и все готово.

...