Есть три вопроса о фрагменте кода ниже.
NO_STUCK_WITH_OPTIMIZATION
) не включен, почему этот фрагмент кода зависает при включенной оптимизации (например, -O1
, -O2
или -O3
), тогда как программа работает хорошо, если оптимизация не включена?std::this_thread::sleep_for()
?ОБНОВЛЕНО:
3. Если is_run
объявить как volatile
(подробнее см. фрагмент кода), то программа никогда не застрянет на на X86?
^^ОБНОВЛЕНО КОНЕЦ^^
#include <functional>
#include <thread>
#include <iostream>
#include <chrono>
#include <atomic>
#ifdef NO_STUCK_WITH_OPTIMIZATION
using TYPE = std::atomic<int>;
#else
using TYPE = int; //The progrom gets stuck if the optimization is enabled.
#endif
int main()
{
TYPE is_run{1};
auto thread = std::thread([&is_run](){while(1==is_run){
//std::this_thread::sleep_for(std::chrono::milliseconds(10)); //If this line is added, the program is no longer gets stuck. Why?
}
std::cout << "thread game over" << std::endl;
});
std::this_thread::sleep_for(std::chrono::seconds(1));
is_run = 0;
thread.join();
}
См. en.cppreference.com/w/cpp/language/…
Извините за мой плохой английский. Мой заголовок надеется указать на два вопроса. Во-первых, почему фрагмент кода зависает, когда оптимизация включена, а программа работает хорошо, если оптимизация не включена. Во-вторых, почему фрагмент кода больше не зависает, если добавить std::this_thread::sleep_for
.
Потому что именно это означает «неопределенное поведение». Без надлежащей безопасности резьбы все гарантии аннулируются. Возврата не предусмотрено. Пусть покупатель будет бдителен. Ты сам по себе. Программа может работать или не работать, а работает она или нет, зависит от параметров компиляции, времени суток, фазы луны, текущей погоды или любого другого фактора. Очень мало значения имеет причина конкретного проявления неопределенного поведения. Это неопределенное поведение.
У вас многопоточная программа. Одна нить делает is_run = 0;
.
Другой поток делает while(1==is_run)
. Хотя вы гарантируете со сном (это вопрос для другого вопроса), что запись выполняется до чтения, вам нужно указать компилятору синхронизировать эту переменную.
В C++ простой способ убедиться, что один поток увидит изменение, — это использовать atomic<int>
. Если вы этого не сделаете, другой поток может никогда не увидеть изменения. Это сокрытие изменения может быть связано с локальной оптимизацией кода во время компиляции, решением ОС не обновлять какую-либо страницу памяти или самим аппаратным обеспечением, решившим, что память не нужно перезагружать.
Использование атомарной переменной гарантирует, что все эти системы знают, что вы хотите сделать. Итак, используйте его. :-)
Поднятие из комментариев:
https://en.cppreference.com/w/cpp/language/memory_model#Threads_and_data_races
A program that has two conflicting evaluations has a data race unless both evaluations execute on the same thread or in the same signal handler, or both conflicting evaluations are atomic operations (see std::atomic), or one of the conflicting evaluations happens-before another (see std::memory_order) If a data race occurs, the behavior of the program is undefined.
Если is_run
объявлен как volatile
(подробнее см. фрагмент кода), то программа никогда не застрянет на на X86?
@John Да, с volatile
компилятору не разрешено оптимизировать чтение, поэтому программа будет работать на x86. Но я думаю, что программа все еще технически неопределенное поведение. Она может дать сбой на платформах с более слабыми моделями памяти. Не используйте volatile
для многопоточности, только std::atomic
или около того.
да, не используйте volatile
. Это ключевое слово было добавлено до того, как в C++ появилась модель памяти. Это устранит некоторые проблемы (предотвратив некоторую оптимизацию компилятора), но не все, и ваш код все равно будет иметь UB.
@Jeffrey Джеффри, я создаю новый вопрос, давайте обсудим на здесь.
Просто чтобы убедиться, что ваш вопрос понят, вы спрашиваете: «Почему, когда я использую не потокобезопасные объекты с несколькими потоками выполнения, мой код оказывается сломанным»?