【问题标题】:Synchronization via shared_ptr: ThreadSanitzier false positive?通过 shared_ptr 同步:ThreadSanitzier 误报?
【发布时间】:2017-03-22 20:35:50
【问题描述】:

以下代码通过 shared_ptr 同步:

#include <memory>
#include <thread>
#include <future>
#include <chrono>
#include <cassert>
#include <atomic>

using std::shared_ptr;
using std::async;
using std::launch;
using std::this_thread::sleep_for;
using namespace std::literals;

void f1(shared_ptr<int> p)
{
    sleep_for(50ms); // make sure the other has started
    assert(*p == 42);
    p.reset();
    sleep_for(50ms); // make sure the other has deleted *p
}

void f2(shared_ptr<int> p)
{
    while (p.use_count() != 1)
    {
        sleep_for(1ms);
    }
    p.reset();
    sleep_for(50ms);
}

int main()
{
    shared_ptr<int> p(new int(42));
    auto t1 = async(launch::async, f1, p);
    auto t2 = async(launch::async, f2, p);

    p.reset();

    t1.get();
    t2.get();

    return 0;
}

我用这个编译

clang++-4.0 -std=c++1z -stdlib=libc++ -Wall -g -O3 -march=native -fsanitize=thread -fno-omit-frame-pointer -pthread sharedPtr.cc -o sharedPtr

运行时,ThreadSanitizer 给了我以下问题:

==================
WARNING: ThreadSanitizer: data race (pid=273)
  Write of size 8 at 0x7b0400000000 by thread T2:
    #0 operator delete(void*) ??:? (sharedPtr+0x4b4af1)
    #1 std::__1::default_delete<int>::operator()(int*) const /usr/include/c++/v1/memory:2516 (discriminator 1) (sharedPtr+0x4b74d8)
    #2 std::__1::__shared_ptr_pointer<int*, std::__1::default_delete<int>, std::__1::allocator<int> >::__on_zero_shared() /usr/include/c++/v1/memory:3759 (discriminator 1) (sharedPtr+0x4b74d8)
[...]

  Previous read of size 4 at 0x7b0400000000 by thread T1:
    #0 f1(std::__1::shared_ptr<int>) /home/dv/src/git/c++-concurrency/test/sharedPtr.cc:22 (sharedPtr+0x4b6fca)
    #1 _ZNSt3__18__invokeIPFvNS_10shared_ptrIiEEEJS2_EEEDTclclsr3std3__1E7forwardIT_Efp_Espclsr3std3__1E7forwardIT0_Efp0_EEEOS5_DpOS6_ /usr/include/c++/v1/__functional_base:415 (sharedPtr+0x4b78cf)
[...]

我假设 C++ 通过 shared_ptr 引用计数保证足够的同步,通过 shared_ptr 读取永远不会与删除器竞争(对于不同的 shared_ptr 对象)。而且我希望这是很常见的用法,所以我很惊讶 ThreadSanitizer 抱怨这个。

所以这是我的问题:

  1. 我的使用安全吗(以及我对 shared_ptr 同步的假设是否正确)? (我希望答案是肯定的,所以现在请关注我真正的问题:)
  2. libc++ 是否正确实现同步?
  3. ThreadSanitizer 真的看不到通过引用计数的同步吗?

【问题讨论】:

  • 好的,我的第一个问题可能在 SO 上被回答了好几次。我添加它只是为了完整性。我真正的问题是第三个。
  • 我正在撤回我的重复关闭请求。请更正您的问题并将前两点写为假设,以便人们了解要关注的部分。

标签: c++ multithreading shared-ptr thread-sanitizer


【解决方案1】:

我的使用安全吗(以及我对 shared_ptr 同步的假设是否正确)?

我在标准中没有看到任何需要use_count() 的实现中使用内存栅栏的内容。 cppreference 表示大多数实现使用memory_order_relaxed 进行读取,因此无法保证排序。

引用:“在多线程环境中,use_count 返回的值是近似值(典型实现使用 memory_order_relaxed 加载)”

因此,严格来说,我认为将use_count() 用作信号量并不安全,因为它依赖于对实现的假设。

libc++ 是否正确实现了同步?

是的

ThreadSanitizer 真的看不到通过引用计数的同步吗?

我认为 ThreadSanitizer 正确地呼唤你。

【讨论】:

  • 我同意 use_count() 不提供同步,我也没有那样使用它。同步由 p.reset() 提供(即内部引用计数的操作)。
  • @CraigP p.reset()p.use_count() 是同一数据流管道的两端。在use_count 的提取中使用memory_order_relaxed 意味着第二个线程可能不会观察到更改,因为可以重新排序提取。即使sleep_for 也并不严格要求发出内存栅栏。代码是一场竞赛。它恰好适用于 386,但不需要在所有架构上完成。
  • sleep_for 仅用于保证 ThreadSanitizer 抱怨的模式。我希望它不会引入(获取/发布)同步,否则我的测试会测试我想要的。
  • [所以当我点击 时提交了我的其他评论,并且由于某些 5 分钟规则而不允许我对其进行编辑...] @richard 我仍然声称我不使用 use_count () 用于同步。它仅用于预测我要测试​​的测试模式(即 f2() 删除对象)。同步仅在 reset() 调用之间进行:f1() 中的 p.reset() 与 f2() 中的 p.reset() 同步,因此 f2() 中的引用计数为零并且对象被删除。跨度>
  • @Richard 怎么样?当我输入f2() 时,p.use_count() 将是 1 或更大。鉴于所有三个线程的并发进度,其他线程中的p.reset() 将在某个时间被调用,并且假设硬件公平,即使use_count() 内部的轻松负载最终将读取 1。
猜你喜欢
  • 2012-12-21
  • 1970-01-01
  • 2011-01-30
  • 2013-05-13
  • 1970-01-01
  • 1970-01-01
  • 2023-04-03
  • 2018-08-23
  • 2011-01-04
相关资源
最近更新 更多