Стандарт C ++ только ограничивает наблюдаемое поведение абстрактной машины в правильно сформированных программах без неопределенного поведения в любом месте во время выполнения абстрактной машины.
Он не дает никаких гарантий относительно отображения между физическими аппаратными действиями, которые выполняет программа, иповедение.
В ваших случаях на абстрактной машине нет порядка между выполнением thread1
и thread2
.Даже если физическое оборудование, на которое нужно запланировать и запустить thread1
до thread2
, это накладывает нулевые ограничения (в вашем простом примере) на выходные данные, которые генерирует программа.Вывод программ ограничен только тем, какие легальные выходные данные может генерировать абстрактная машина.
Компилятор C ++ может легально:
Полное удаление вашей программы как эквивалент возврата 0;
Докажите, что чтение cancel_work
в thread2
не секвенировано относительно всех модификаций cancel_work
вдали от 0
, и измените его на постоянное чтение 0
.
На самом деле сначала запустите thread1
, затем запустите thread2
, но докажите, что он может обрабатывать операции в thread2
так, как если бы они произошли до запуска thread1
, поэтомуне пытайтесь принудительно обновить строку кэша в thread2
и считывать устаревшие данные из cancel_work
.
То, что на самом деле происходит на оборудовании, не влияет на то, чтопрограмма может легально делать.И то, что программа может делать по закону, - в ситуациях с потоками ограничивается наблюдаемым поведением абстрактной машины, а также поведением примитивов синхронизации и их использованием в разных потоках.
Для того, чтобы фактические события произошли до возникновения отношений,вам нужно что-то вроде:
std::thread(thread1_func).join();
std::thread(thread2_func).join();
и теперь мы знаем, что все в thread1_func
происходит до thread2_func
.
Мы все еще можем переписать вашу программу как return 0;
и аналогичные изменения,Но теперь у нас есть гарантия того, что thread1_func
произойдет раньше, чем код thread2_func
.
Обратите внимание, что мы можем устранить (1) выше с помощью:
std::lock_guard<std::mutex> lock(mutex);
int tmp = cancel_work; //Will tmp be 1 or 0?
std::cout << tmp;
и заставить tmp
на самом деле будет напечатано.
Программа может быть преобразована в ту, которая печатает 1
или 0
и не имеет никакой многопоточности.Это могло бы сохранить поток, но измените thread2_func
, чтобы вывести константу 0
.И т.д.
Итак, мы переписываем вашу программу так:
std::condition_variable cv;
bool writ = false;
int cancel_work = 0; //any difference if replaced with std::atomic<int> in this case?
std::mutex mutex;
// Thread 1 executes this function
void thread1_func()
{
{
std::lock_guard<std::mutex> lock(mutex);
cancel_work = 1;
}
{
std::lock_guard<std::mutex> lock(mutex);
writ = true;
cv.notify_all();
}
}
// Thread 2 executes this function
void thread2_func()
{
std::unique_lock<std::mutex> lock(mutex);
cv.wait(lock, []{ return writ; } );
int tmp = cancel_work;
std::cout << tmp; // will print 1
}
int main()
{
std::thread t1(thread1_func);
std::thread t2(thread2_func);
t1.join(); t2.join();
return 0;
}
и теперь thread2_func
происходит после thread1_func
и все хорошо.Чтение гарантировано будет 1
.