С++ 11 (g++ thread sanitized) Заказ неатомических операций с атомикой (ложный положительный?)
Я экспериментирую с g++ и дезинфицирующим средством для потоков, и я думаю, что получаю ложные срабатывания. Это правда, или я делаю какую-то большую ошибку?
Программа (вырезать и вставить из Энтони Уильямса: С++ Concurrency в действии, стр. 145, список 5.13)
#include <atomic>
#include <thread>
#include <assert.h>
bool x=false;
std::atomic<bool> y;
std::atomic<int> z;
void write_x_then_y()
{
x=true;
std::atomic_thread_fence(std::memory_order_release);
y.store(true,std::memory_order_relaxed);
}
void read_y_then_x()
{
while(!y.load(std::memory_order_relaxed));
std::atomic_thread_fence(std::memory_order_acquire);
if(x)
++z;
}
int main()
{
x=false;
y=false;
z=0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load()!=0);
}
Скомпилировано с помощью
g++ -o a -g -Og -pthread a.cpp -fsanitize=thread
g++ версия
~/build/px> g++ -v
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/6.1.1/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../configure --enable-bootstrap --enable-languages=c,c++,objc,obj-c++,fortran,ada,go,lto --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --with-bugurl=http://bugzilla.redhat.com/bugzilla --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-plugin --enable-initfini-array --disable-libgcj --with-isl --enable-libmpx --enable-gnu-indirect-function --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix
gcc version 6.1.1 20160621 (Red Hat 6.1.1-3) (GCC)
Я получаю:
~/build/px> ./a
==================
WARNING: ThreadSanitizer: data race (pid=13794)
Read of size 1 at 0x000000602151 by thread T2:
#0 read_y_then_x() /home/ostri/build/px/a.cpp:17 (a+0x000000401014)
#1 void std::_Bind_simple<void (*())()>::_M_invoke<>(std::_Index_tuple<>) /usr/include/c++/6.1.1/functional:1400 (a+0x000000401179)
#2 std::_Bind_simple<void (*())()>::operator()() /usr/include/c++/6.1.1/functional:1389 (a+0x000000401179)
#3 std::thread::_State_impl<std::_Bind_simple<void (*())()> >::_M_run() /usr/include/c++/6.1.1/thread:196 (a+0x000000401179)
#4 <null> <null> (libstdc++.so.6+0x0000000baaae)
Previous write of size 1 at 0x000000602151 by thread T1:
#0 write_x_then_y() /home/ostri/build/px/a.cpp:9 (a+0x000000400fbd)
#1 void std::_Bind_simple<void (*())()>::_M_invoke<>(std::_Index_tuple<>) /usr/include/c++/6.1.1/functional:1400 (a+0x000000401179)
#2 std::_Bind_simple<void (*())()>::operator()() /usr/include/c++/6.1.1/functional:1389 (a+0x000000401179)
#3 std::thread::_State_impl<std::_Bind_simple<void (*())()> >::_M_run() /usr/include/c++/6.1.1/thread:196 (a+0x000000401179)
#4 <null> <null> (libstdc++.so.6+0x0000000baaae)
Location is global 'x' of size 1 at 0x000000602151 (a+0x000000602151)
Thread T2 (tid=13797, running) created by main thread at:
#0 pthread_create <null> (libtsan.so.0+0x000000028380)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0x0000000badc4)
#2 main /home/ostri/build/px/a.cpp:26 (a+0x000000401097)
Thread T1 (tid=13796, finished) created by main thread at:
#0 pthread_create <null> (libtsan.so.0+0x000000028380)
#1 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) <null> (libstdc++.so.6+0x0000000badc4)
#2 main /home/ostri/build/px/a.cpp:25 (a+0x00000040108a)
SUMMARY: ThreadSanitizer: data race /home/ostri/build/px/a.cpp:17 in read_y_then_x()
==================
ThreadSanitizer: reported 1 warnings
Я получил это предупреждение в более сложной программе, и я подумал, что это моя ошибка, но теперь даже "программа школьных книг" демонстрирует такое же поведение.
Это (т.е. Какой-то компилятор отсутствует) me или g++?
ОБНОВЛЕНО
Взято из ссылка
#if defined(__SANITIZE_THREAD__)
#define TSAN_ENABLED
#elif defined(__has_feature)
#if __has_feature(thread_sanitizer)
#define TSAN_ENABLED
#endif
#endif
#ifdef TSAN_ENABLED
#define TSAN_ANNOTATE_HAPPENS_BEFORE(addr) \
AnnotateHappensBefore(__FILE__, __LINE__, (void*)(addr))
#define TSAN_ANNOTATE_HAPPENS_AFTER(addr) \
AnnotateHappensAfter(__FILE__, __LINE__, (void*)(addr))
extern "C" void AnnotateHappensBefore(const char* f, int l, void* addr);
extern "C" void AnnotateHappensAfter(const char* f, int l, void* addr);
#else
#define TSAN_ANNOTATE_HAPPENS_BEFORE(addr)
#define TSAN_ANNOTATE_HAPPENS_AFTER(addr)
#endif
#include <atomic>
#include <thread>
#include <assert.h>
bool x=false;
std::atomic<bool> y;
std::atomic<int> z;
void write_x_then_y()
{
x=true;
std::atomic_thread_fence(std::memory_order_release);
TSAN_ANNOTATE_HAPPENS_BEFORE(&x);
y.store(true,std::memory_order_relaxed);
}
void read_y_then_x()
{
while(!y.load(std::memory_order_relaxed));
std::atomic_thread_fence(std::memory_order_acquire);
TSAN_ANNOTATE_HAPPENS_AFTER(&x);
if(x)
++z;
}
{
x=false;
y=false;
z=0;
std::thread a(write_x_then_y);
std::thread b(read_y_then_x);
a.join();
b.join();
assert(z.load()!=0);
}
Скомпилировать команду
g++ -o a -g -Og -pthread a.cpp -fsanitize=thread -D__SANITIZE_THREAD__
Ответы
Ответ 1
TL; DR: Это ложное срабатывание TSAN. Код действителен.
Тема 1:
x=true; // W
std::atomic_thread_fence(std::memory_order_release); // A
y.store(true,std::memory_order_relaxed); // X
Тема 2:
while(!y.load(std::memory_order_relaxed)); // Y
std::atomic_thread_fence(std::memory_order_acquire); // B
if(x) // R
++z;
[atomics.fences]/2:
Заборный забор A синхронизируется с заборным ограждением B, если существует атомные операции X и Y, работающие на каком-то атомарном объекте M, так что A секвенируется до X, X изменяет M, Y секвенируется до B и Y считывает значение, записанное X, или значение, написанное любой стороной эффект в гипотетической последовательности X освобождения, если бы это было релиз.
Перейдите в список:
- [✔] "существуют атомные операции X и Y, работающие на каком-то атомарном объекте M": очевидно. M -
y
.
- [✔] "А секвенирован до X": очевидно ([intro.execution]/14 для тех, кто хочет цитату).
- [✔] "X изменяет M": очевидно.
- [✔] "Y секвенирован до B": очевидно.
- [✔] ", а Y читает значение, записанное X...": это единственный способ, которым этот цикл может завершиться.
Следовательно, выпускной затвор A синхронизируется с приобретающим ограждением B.
Запись W секвенирована до A, а чтение R секвенируется после B, поэтому W inter-thread происходит раньше, и так происходит раньше, R. [intro.races]/9-10:
Оценка. Межпоточная передача происходит до оценки B, если
- A синхронизируется с B или
- A - это зависимость, упорядоченная до B, или
- для некоторой оценки X
- A синхронизируется с X и X секвенируется до B, или
- A секвенирован до того, как X и X межпоточные события произойдут до B, или
- Межпоточный процесс происходит до того, как X и X межпоточные события произойдут до B.
Оценка A происходит до оценки B (или, что эквивалентно, B происходит после A), если:
- A секвенирован до B или
- Интер-поток происходит до B.
Нет расы данных из-за отношений между событиями ([intro.races]/19):
Выполнение программы содержит гонку данных, если она содержит два потенциально параллельные конфликтующие действия, по крайней мере один из которых не атомарным, и не происходит другого, кроме специальный случай для обработчиков сигналов, описанных ниже. Любая такая гонка данных приводит к поведению undefined.
Кроме того, чтению R гарантируется считывание значения, записанного W, потому что W - видимый побочный эффект, при этом не было другого побочного эффекта на x
после начала потоков ([intro.races]/11):
Видимый побочный эффект A на скалярном объекте или битовом поле M с учетом к вычислению значения B из M удовлетворяет условиям:
- A происходит до B и
- нет другого побочного эффекта от X до M, так что A происходит до того, как X и X произойдет до B.
Значение неатомного скалярного объекта или битового поля M, как определено по оценке B, должно быть значение, сохраненное видимым побочным эффектом А.
Ответ 2
memory_order_relaxed
не накладывает ограничений на переупорядочение.
memory_order_acquire
не препятствует переупорядочению за ограждением сверху. Это предотвращает только заказы снизу. Это означает, что код может быть выполнен следующим образом:
std::atomic_thread_fence(std::memory_order_acquire);
if(x)
++z;
while(!y.load(std::memory_order_relaxed));
Это приведет к тому, что гонка данных будет считана в рангах if(x)
с помощью x=true
.
Вам нужны ограждения с семантикой memory_order_acq_rel
или memory_order_seq_cst
в обеих функциях, что предотвращает переупорядочение в обоих направлениях.
Ответ 3
К сожалению, ThreadSanitizer не может понять память. Это связано с тем, что он объясняет, что происходит, - до отношения между обращениями к определенным объектам, и нет объекта в операции забора.
Если вы замените ослабленную нагрузку + приобретаете ограждение с нагрузкой на захват, а освобождающий забор + расслабленный магазин с хранилищем релизов, TSan будет правильно определять связь между хранилищем и нагрузкой между ними.
Также обратите внимание, что реализация GCC TSan может не обрабатывать атомику на O0 (см. fooobar.com/info/418660/...).