Предупреждение о дезинфекции потока Clang при использовании std::string в многопоточной среде
Во время работы с дезинфицирующим средством для хлопковых нитей мы заметили предупреждения гонки данных. Мы считаем, что технология std::string copy-on-write не является потокобезопасной, но мы можем ошибаться. Мы уменьшили предупреждение, которое мы видели в этом коде:
void test3() {
std::unique_ptr<std::thread> thread;
{
auto output = make_shared<string>();
std::string str = "test";
thread.reset(new std::thread([str, output]() { *output += str; }));
// The str string now goes out of scope but due to COW
// the captured string may not have the copy of the content yet.
}
thread->join();
}
При компиляции с включенным дезинфицирующим средством потока:
clang++ -stdlib=libc++ -std=c++11 -O0 -g -fsanitize=thread -lpthread -o test main.cpp
или
clang++ -std=c++11 -O0 -g -fsanitize=thread -lpthread -o test main.cpp
И когда он запускается несколько раз, он в конечном итоге выдает это предупреждение:
WARNING: ThreadSanitizer: data race (pid=30829)
Write of size 8 at 0x7d0c0000bef8 by thread T62:
#0 operator delete(void*) <null>:0
...
Previous write of size 1 at 0x7d0c0000befd by thread T5:
#0 std::__1::char_traits<char>::assign(char&, char const&) string:639
...
Является ли это ложным положительным результатом дезинфицирующего средства для потока или это настоящая гонка данных? Если позднее,
можно ли обходиться без изменения кода (например, передавая некоторые флаги компилятору), является ли это ошибкой в реализации строки (или что-то еще)?
UPDATE: выходы clang --version:
Ubuntu clang version 3.5-1ubuntu1 (trunk) (based on LLVM 3.5)
Target: x86_64-pc-linux-gnu
Thread model: posix
UPDATE: cpp Я использую, чтобы воспроизвести это предупреждение.
Ответы
Ответ 1
[править] Предположения ниже оказались ошибочными, см. ссылку в комментариях. T5, а не T62 - это поток, порожденный в коде выше.
Было бы полезно понять идентификаторы потоков, но я предполагаю, что T5 является основным потоком, а T62 - порожденной нитью. Похоже, что копия сделана на основном потоке (до того, как новая нить рассеивается) и уничтожена на новом потоке (очевидно). Это безопасно, потому что новый поток не может участвовать в гонке с основным потоком до его существования.
Следовательно, это ошибка дезинфицирующего средства для потока. Не удалось проверить, существовал ли поток T62 во время предыдущей записи.
Забастовкa >
Ответ 2
Это довольно сложно. Я обобщил логику в вашем коде ниже:
In thread T62:
Create string s (with reference count)
Create output_1 pointing to s in the thread storage for T62
Create thread T5
Create output_2 pointing to s in the thread storage for T5
Sync point
In thread T5:
Append to s ** MODIFY **
Thread-safe decrement of reference count for s (not a sync point)
End of output_2 lifetime
Exit
In thread T62:
Thread-safe decrement of reference count for s (not a sync point)
End of output_1 lifetime
Deallocate s ** MODIFY **
Join
Sync point
In thread T62:
Destroy T5
Насколько я могу судить, стандарт не дает никаких гарантий относительно синхронизации в отношении вызова shared_ptr
deleter:
(20.8.2.2/4). Для определения наличия расы данных функции-члены должны получать доступ и изменять только объекты shared_ptr и weak_ptr, а не объекты, на которые они ссылаются.
Я предполагаю, что любые модификации, которые действительно происходят с указанным объектом при вызове функции-члена shared_ptr, такие как любые изменения, которые может сделать удаляющий, считаются выходящими за рамки shared_ptr
, и поэтому shared_ptr
не несет ответственности за то, чтобы они не вводили гонку данных. Например, изменения, внесенные в строку с помощью T5, могут быть недоступны для T62 тем временем, когда T62 пытается его уничтожить.
Тем не менее, Херб Саттер в своем разговоре "Atomic < > weapon" указал, что он видел это как ошибку, чтобы иметь атомный декремент счетчика ссылок в деструкторе shared_ptr
без семантики получения и выпуска, m не уверен, как это нарушает стандарт.