【问题标题】:Thread Safety of Shared Pointers' Control Block共享指针控制块的线程安全
【发布时间】:2021-03-17 18:19:00
【问题描述】:

我正在开发一个使用共享指针的小程序。我有一个简单的类“事物”,它只是一个具有整数属性的类:


class Thing{
public:
    Thing(int m){x=m;}
    int operator()(){
        return x;
    }
    void set_val(int v){
        x=v;
    }
    int x;
    ~Thing(){
        std::cout<<"Deleted thing with value "<<x<<std::endl;
    }

};

我有一个简单的函数“fun”,它接受一个 shared_ptr 实例和一个整数值 index,它只是跟踪哪个线程正在输出给定值。函数打印出传递给函数的索引值,以及作为参数传入的共享指针的引用计数

std::mutex mtx1;
void fun(std::shared_ptr<Thing> t1,int index){
    std::lock_guard <std::mutex> loc(mtx1);
    int m=t1.use_count();
    std::cout<<index<<" : "<<m<<std::endl;
}

在 main 中,我创建了一个共享指针实例,它是一个 Thing 对象的包装器,如下所示:

    std::shared_ptr<Thing> ptr5(nullptr);
    ptr5=std::make_shared<Thing>(110);

(以这种方式声明是为了异常安全)。

然后我创建 3 个线程,每个线程创建一个线程来执行 fun() 函数,该函数将 ptr5 共享指针作为参数并增加索引值:

    std::thread t1(fun,ptr5,1),t2(fun,ptr5,2),t3(fun,ptr5,3);
    t1.join();
    t2.join();
    t3.join();

我的想法是,由于每个共享指针控制块成员函数都是线程安全的,所以在 fun() 函数中调用 use_count() 不是原子指令,因此不需要锁定。但是,无论在没有和没有 lock_guard 的情况下运行,这仍然会导致竞争条件。我希望看到以下输出:

1:2 2:3 3:4

由于每个线程都会产生一个对原始共享指针的新引用,因此 use_count() 将每次增加 1 个引用。但是,由于某些竞争条件,我的输出仍然是随机的。

【问题讨论】:

  • > fun() 函数中对 use_count() 的调用不是原子指令,因此不需要锁定我认为您对什么是原子以及什么需要锁定有点困惑。跨度>
  • 你能扩展一下吗?我仍然习惯于何时使用锁定以及什么被认为是原子指令。
  • 原子指令是那些与std::atomic 绑定的指令。如果您从 2 个线程同时访问一个非原子对象,则需要一个锁(除非另有说明,例如 std::mutex::lock)。在您的示例中,您从 3 个不同的线程访问 3 个 不同 shared_ptr 对象。此外,shared_ptr::use_count 是常量。所以不需要锁。在评论部分有点难以扩展,但请参阅例如:stackoverflow.com/q/14127379/666785

标签: c++ multithreading smart-pointers


【解决方案1】:

在多线程环境中,use_count() 是近似值。来自cppreference

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

shared_ptr 的控制块是线程安全的:

所有成员函数(包括复制构造函数和复制赋值)都可以由多个线程在 shared_ptr 的不同实例上调用而无需额外同步,即使这些实例是副本并且共享同一对象的所有权。

看到过期的use_count() 并不表示控制块已被竞争条件破坏。

请注意,这不会扩展到修改指向对象。它不为指向的对象提供同步。只有shared_ptr的状态和控制块受到保护。

【讨论】:

  • 我知道 shared_ptr 中的对象本身不是线程安全的,但是在这种情况下,您所说的近似是什么意思?这是否意味着没有真正的同步方法?
  • @ElliottGoldstein 这意味着在多线程上下文中没有使用use_count() 的有用方法。
  • @ElliottGoldstein 在您阅读计数器时它可能已经改变了。
  • 请注意,在您的测试中,可以有更多的shared_ptr 实例存储在std::thread 中,并且您的一些、全部或没有测试用例可能在下一个线程之前完成开始,这意味着同时发生的实例可能比您预期的要少。
猜你喜欢
  • 2017-05-14
  • 2017-06-29
  • 1970-01-01
  • 2012-08-19
  • 1970-01-01
  • 1970-01-01
  • 2013-11-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多