在多线程环境中使用std :: string时,Clang的线程清理程序警告

时间:2015-01-01 10:58:33

标签: c++ multithreading c++11 thread-safety clang++

在使用clang的线程消毒剂时,我们注意到数据争用警告。我们认为这是由于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
    ...

这是线程消毒器的假阳性还是真正的数据竞争?如果以后, 它可以在不改变代码的情况下工作(例如通过将一些标志传递给编译器),这是字符串实现中的已知错误(或其他东西)吗?

更新: clang --version 输出:

Ubuntu clang version 3.5-1ubuntu1 (trunk) (based on LLVM 3.5)
Target: x86_64-pc-linux-gnu
Thread model: posix

更新:The cpp我用来重现此警告。

2 个答案:

答案 0 :(得分:2)

[edit]下面的假设结果是错误的,请参阅评论中的链接。 T5,而不是T62是上面代码中产生的线程。

<击> 理解线程ID是有用的,但我假设T5是主线程,T62是生成线程。看起来副本是在主线程上(在新线程被spwaned之前)并在新线程上销毁(显然)。这是安全的,因为新线程在存在之前不能与主线程竞争。

<击>

因此,这是一个线程清理程序错误。它无法检查上一次写入时是否存在线程T62。

答案 1 :(得分:1)

这非常棘手。我在下面的代码中总结了逻辑:

    
    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删除器的同步:

  

(20.8.2.2/4)为了确定是否存在数据争用,成员函数只能访问和修改shared_ptr和weak_ptr对象本身而不是它们所引用的对象。

我认为这意味着在调用shared_ptr的成员函数时实际发生在指向对象上的任何修改,例如删除者可能做出的任何修改,都被认为超出了{{{ 1}},因此shared_ptr不负责确保他们不引入数据竞争。例如,在T62尝试销毁它时,T62对字符串所做的修改可能对T62不可见。

然而,Herb Sutter在他的“Atomic&lt;&gt;武器”谈话中表示,他认为这是一个错误,导致shared_ptr析构函数中的引用计数的原子减量没有获取和释放语义,但我不确定它是如何违反标准的。