Mutex и переменные обновления - PullRequest
       16

Mutex и переменные обновления

0 голосов
/ 13 сентября 2018

Нужна проверка мыслительного процесса, скажем, я настроил поток следующим образом

bool threadRun=true;
std::mutex threadMutex;
std::condition_variable event;
std::thread processThread(process);

void process()
{
    while(threadRun)
    {
        if(noWork())
        {
            std::unique_lock<std::mutex> lock(threadMutex);

            if(threadRun)
            {
                if(!getWork()) //try to get work, return false if no work
                    event.wait(lock);
            }

            continue;
        }

        doWork();
    }
}

void stopThread()
{
    {
        std::unique_lock<std::mutex> lock(threadMutex);
        threadRun=false;
    }
    event.notify_all();
    processThread.join();
}

Так как threadRun не находится под блокировкой мьютекса в потоке, нет гарантии, что вызов stopThread фактически остановит потоктак как очистка кэша не требуется, когда есть работа todo. Однако, когда нет работы и блокировка снята, это должно вызвать очистку кэша и гарантированно обновится threadRun, верно?

1 Ответ

0 голосов
/ 13 сентября 2018

Гонки данных в C ++ - неопределенное поведение.

Теперь вы можете подумать, что это означает "ну, я в порядке, когда они пропустили чтение, они в конце концов получат его". Но UB - это то, что компилятор может предположить, что не произойдет .

Тот факт, что вы читаете threadRun без блокировки или другой синхронизации, означает, что компилятор может, может и должен предполагать, что никто в любом другом потоке не изменит threadRun.

Таким образом, код можно оптимизировать до:

if(threadRun)
  while(true)

т. Е. Компилятор может прочитать threadRun один раз, а затем не беспокоиться о его повторном чтении, если он может доказать, что никто не может изменить его значение определенным образом.

Теперь, если компилятор не может доказать это, он вместо этого должен прочитать threadRun. Таким образом, ваш UB ведет себя так, как будто он «должен» работать. А на некоторых платформах само оборудование кэшировало threadRun в кеше для каждого процессора (или потока), и тот факт, что другой поток записывал в него, не отражается в вашем рабочем потоке в течение некоторого неизвестного периода времени (возможно, навсегда).

Если пойти дальше, умный компилятор может заметить, что вы читаете threadRun без синхронизации. Затем он может использовать это, чтобы доказать, что никто, с синхронизацией или без нее, не может писать в threadRun в другом потоке.

Как только он докажет это, он может исключить проверку threadRun в мьютексе.

Не выполняйте неопределенное поведение, если только вы не хотите проверять сборку, которую ваша программа производит с этого момента и до конца времени, каждый раз, когда вы ее компилируете, даже если она «работает» или если выигрыш огромен и стоит риска.

Заменить threadRun на std::atomic_bool. В худшем случае вы потеряете немного производительности и получите правильность.

...