【问题标题】:Is reference counting thread safe引用计数线程是否安全
【发布时间】:2015-03-14 11:11:22
【问题描述】:

例如考虑

class ProcessList {
private
   std::vector<std::shared_ptr<SomeObject>> list;
   Mutex mutex;
public:
   void Add(std::shared_ptr<SomeObject> o) { 
       Locker locker(&mutex); // Start of critical section. Locker release so will the mutex - In house stuff
       list.push_back(std::make_shared<SomeObject>(o).
   }

   void Remove(std::shared_ptr<SomeObject> o) { 
       Locker locker(&mutex); // Start of critical section. Locker release so will the mutex - In house stuff
       // Code to remove said object but indirectly modifying the reference count in copy below
   }

   void Process() {
       std::vector<std::shared_ptr<SomeObject>> copy;

       {
           Locker locker(&mutes);
           copy = std::vector<std::shared_ptr<SomeObject>>(
                list.begin(), list.end()
           )
       }
       for (auto it = copy.begin(), it != copy.end(); ++it) {
           it->Procss(); // This may take time/add/remove to the list
       }
    }
};

一个线程运行Process。多个线程运行添加/删除。

引用计数是否安全且始终正确 - 还是应该在其周围放置互斥体?

【问题讨论】:

  • 引用计数可以线程安全地实现。考虑到你有 C++11,它也有线程,我假设引用计数也是以线程安全的方式实现的。顺便说一句:即使是来自 Boost 的原始 shared_ptr 也已经是线程安全的。
  • 每个引用计数有互斥体吗?
  • 不,他们使用原子操作。您可以为每个计数器使用互斥锁来实现它们,但这将是低效的。您还可以使用单个全局互斥体来实现它们,这会更简单,但可能仍然比使用原子效率低。
  • @UlrichEckhardt 的标准要求是这种情况(shared_ptr 是线程安全的)?如果可以,可以给个参考吗?谢谢。
  • duckduckgo.com/?q=C%2B%2B+shared_ptr+thread+safe 这由第一个链接回答。

标签: c++ multithreading


【解决方案1】:

是的,旨在要求引用计数是线程安全的标准(在 §20.8.2.2 中,至少从 N3997 开始)。

对于像Add这样的简单案例:

void Add(std::shared_ptr<SomeObject> o) { 
    Locker locker(&mutex);
    list.push_back(std::make_shared<SomeObject>(o).
}

...标准中的保证足够强大,您不需要互斥锁,因此您可以:

void Add(std::shared_ptr<SomeObject> o) { 
    list.push_back(std::make_shared<SomeObject>(o).
}

对于您的某些操作,线程安全引用计数是否一定会消除您的互斥锁,这一点并不明确。例如,在您的 Process 中,您有:

   {
       Locker locker(&mutes);
       copy = std::vector<std::shared_ptr<SomeObject>>(
            list.begin(), list.end()
       )
   }

这会将整个复制作为原子操作执行——在复制期间没有其他内容可以修改列表。这可确保您的副本为您提供与开始副本时完全相同的列表快照。如果您消除互斥体,引用计数仍将起作用,您的副本可能会反映在制作副本时所做的更改。

换句话说,shared_ptr 的线程安全性仅确保每个单独的递增或递减都是原子的——它不能确保整个列表的操作都是原子的,就像在这种情况下互斥锁所做的那样。

由于您的list 实际上是vector,您应该能够将复制代码稍微简化为copy = list

另请注意,您的 Locker 似乎是 std::lock_guard 提供的一个子集。看来您可以使用:

std::lock_guard<std::mutex> locker(&mutes);

...很容易就位。

【讨论】:

  • 但是向量线程也安全吗?我认为并非如此,两者之间存在相互作用。无论如何,我们的储物柜在日志记录方面做了一些额外的工作。
  • @EdHeal:没有什么可以禁止向量实现提供线程安全,但这也不是必需的。如果多个线程修改向量,那么是的,您需要使用互斥锁(或其他)来序列化该访问。
  • 它是一个共享指针向量。 - 所以物品是安全的,但向量不是 - 正确吗
  • @EdHeal:就是这个想法,是的。
【解决方案2】:

使用互斥体进行引用计数将是一种开销。

在内部,互斥锁使用原子操作,基本上互斥锁执行内部线程安全引用计数。因此,您可以直接使用原子来进行引用计数,而不是使用互斥体,这实际上是做双倍的工作。

【讨论】:

    【解决方案3】:

    除非您的 CPU 架构具有原子增量/减量,并且您将其用于引用计数,否则,不,这不安全; C++ 不保证任何标准类型上的 x++/x-- 操作的线程安全。

    如果您的编译器支持 (C++11),请使用 atomic&lt;int&gt;,否则您将需要锁定。

    更多参考资料:

    【讨论】:

    • 问题是std::shared_ptr(这是OP提供的代码中使用的)是否是线程安全的。这里没有“x++/x--”操作(除非在shared_ptr实现中)。
    猜你喜欢
    • 2021-10-26
    • 1970-01-01
    • 1970-01-01
    • 2016-06-26
    • 1970-01-01
    • 1970-01-01
    • 2012-08-21
    • 2016-09-05
    相关资源
    最近更新 更多