O código abaixo produz um aviso ao executar com desinfetante de threads no macOS. Não consigo ver onde está a corrida. O bloco de controle shared_ptr e weak_ptr é seguro para threads, e empurrar e saltar do std::queue
é feito com um bloqueio mantido.
#include <future>
#include <memory>
#include <queue>
class Foo {
public:
Foo() {
fut = std::async(std::launch::async, [this] {
while (!shouldStop) {
std::scoped_lock lock(mut);
while (!requests.empty()) {
std::weak_ptr<float> requestData = requests.front();
requests.pop();
(void)requestData;
}
}
});
}
~Foo() {
shouldStop.store(true);
fut.get();
}
void add(const std::weak_ptr<float> subscriber) {
std::scoped_lock lock(mut);
requests.push(subscriber);
}
private:
std::atomic<bool> shouldStop = false;
std::future<void> fut;
std::queue<std::weak_ptr<float>> requests;
std::mutex mut;
};
int main() {
Foo foo;
int numIterations = 100000;
while (--numIterations) {
auto subscriber = std::make_shared<float>();
foo.add(subscriber);
subscriber.reset();
}
std::this_thread::sleep_for(std::chrono::seconds(1));
}
Aviso com stacktrace:
WARNING: ThreadSanitizer: data race (pid=11176)
Write of size 8 at 0x7b0800000368 by thread T1 (mutexes: write M16):
#0 operator delete(void*) <null>:1062032 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x4f225)
#1 std::__1::__shared_ptr_emplace<float, std::__1::allocator<float> >::__on_zero_shared_weak() new:272 (minimal:x86_64+0x1000113de)
#2 std::__1::weak_ptr<float>::~weak_ptr() memory:5148 (minimal:x86_64+0x100010762)
#3 std::__1::weak_ptr<float>::~weak_ptr() memory:5146 (minimal:x86_64+0x100002448)
#4 Foo::Foo()::'lambda'()::operator()() const minimal_race.cpp:15 (minimal:x86_64+0x10000576e)
#5 void std::__1::__async_func<Foo::Foo()::'lambda'()>::__execute<>(std::__1::__tuple_indices<>) type_traits:4345 (minimal:x86_64+0x1000052f0)
#6 std::__1::__async_func<Foo::Foo()::'lambda'()>::operator()() future:2323 (minimal:x86_64+0x100005268)
#7 std::__1::__async_assoc_state<void, std::__1::__async_func<Foo::Foo()::'lambda'()> >::__execute() future:1040 (minimal:x86_64+0x100005119)
#8 void* std::__1::__thread_proxy<std::__1::tuple<std::__1::unique_ptr<std::__1::__thread_struct, std::__1::default_delete<std::__1::__thread_struct> >, void (std::__1::__async_assoc_state<void, std::__1::__async_func<Foo::Foo()::'lambda'()> >::*)(), std::__1::__async_assoc_state<void, std::__1::__async_func<Foo::Foo()::'lambda'()> >*> >(void*) type_traits:4286 (minimal:x86_64+0x10000717c)
Previous atomic write of size 8 at 0x7b0800000368 by main thread:
#0 __tsan_atomic64_fetch_add <null>:1062032 (libclang_rt.tsan_osx_dynamic.dylib:x86_64h+0x24cdd)
#1 std::__1::shared_ptr<float>::~shared_ptr() memory:3472 (minimal:x86_64+0x1000114d4)
#2 std::__1::shared_ptr<float>::~shared_ptr() memory:4502 (minimal:x86_64+0x100002488)
#3 main memory:4639 (minimal:x86_64+0x10000210b)
SUMMARY: ThreadSanitizer: data race new:272 in std::__1::__shared_ptr_emplace<float, std::__1::allocator<float> >::__on_zero_shared_weak()
edit: Eu compilo com:
clang++ -std=c++17 -g -fsanitize=thread -o test minimal_race.cpp
versão clang:
$ clang++ --version
clang version 7.1.0 (tags/RELEASE_710/final)
Target: x86_64-apple-darwin18.7.0
Thread model: posix
InstalledDir: /usr/local/opt/llvm@7/bin
Estou usando o macOS 10.14.6
c++
multithreading
shared-ptr
weak-ptr
thread-sanitizer
tuple_cat
fonte
fonte
std::launch::async
, cabestd::async
determinar como agendar suas solicitações de acordo com cppreference.com . Isso significa que, potencialmente, quandoFoo
é construído, ofuture
bloqueio do mutex (que não é desbloqueado até queshouldStop
seja verdadeiro). E seFoo::Add
for chamado, ele tentará bloquear o mutex, aguardando o futuro para desbloqueá-lo, o que nunca acontece.clang++ -std=c++17 -O0 -ggdb -fsanitize=thread -stdlib=libc++ -o x x.cpp
, não acontece com libstdc ++,Respostas:
Eu acho que isso pode ser um bug com libc ++ e clang em sua implementação fraca_ptr / shared_ptr ...
alterar a fila weak_ptr para um shared_ptr corrige o problema mesmo nas versões antigas do clang.
as mudanças são a linha 25:
linha 33:
e reescrevendo o tempo na linha 42 como:
fonte
shared_ptr
.