0

我正在努力思考如何确保对象引用计数是线程安全的。

class MyObject{
  //Other implementation details
private:
  mutable volatile LONGLONG * m_count;
  IData * m_data;
};

假设有必要的类声明,只是保持简单。这里是拷贝构造函数和析构函数的实现。

MyObject::MyObject(const MyObject& rhs) : m_count(rhs.m_count), m_data(rhs.m_data){
    InterlockedIncrement64(m_count);
}

MyObject::~MyObject(){
    if(InterlockedDecrement64(m_count) == 0)
    delete m_data;
}

这个线程安全吗?如何看到复制构造函数的 intilization 列表,原子与否?这还重要吗?我应该在初始化列表中设置计数的递增值(这可能吗)?

就目前而言,这已经足够好了。我认为它是,否则,我怎么能进入thread1复制和thread2同时销毁的场景count == 1。线程之间必须进行握手,这意味着 thread1 必须在 thread2 的对象超出范围之前完全复制对象,对吗?

在阅读了其中一些回复后,我回去做了一些研究。Boost 非常相似地实现了它们的 shared_ptr。这是析构函数调用。

void release() // nothrow
{
    if( BOOST_INTERLOCKED_DECREMENT( &use_count_ ) == 0 )
    {
        dispose();
        weak_release();
    }
}

有些人建议在 boost 文档中明确指出分配不是线程安全的。我同意和不同意。我认为在我的情况下我不同意。我只需要threadA和threadB之间的握手。我认为某些回复中描述的某些问题在这里并不适用(尽管它们是我没有完全考虑过的令人大开眼界的回复)。

示例 ThreadA atach(SharedObject); //通过值传递的共享对象,计数递增等

ThreadB //接受对象,将其添加到共享对象列表中。ThreadB 位于一个计时器上,该计时器向所有 SharedObjects 通知一个事件。在通知之前,列表的副本由关键部分保护。CS被释放,副本被通知。

ThreadA 分离(SharedObject);//从对象列表中删除共享对象

现在,同时 ThreadB 正在对 SharedOjbect 进行单选,并且在 ThreadA 分离所述共享对象之前已经制作了列表的副本。一切都好吗?

4

4 回答 4

2

从技术上讲,它应该是安全的。

为什么?因为为了复制一个对象,源需要有一个“引用”,所以它不会在复制过程中消失。此外,没有人正在访问当前正在构建的对象。

析构函数也是安全的,因为无论如何都没有引用。

不过,您可能需要重新考虑复制引用计数。这些引用实际上并不存在。每个参考原件的人都必须以某种方式减少副本的引用计数,前提是它在复制之前获得了原始参考。副本应该像新对象一样开始,引用计数为 1。

编辑:同样,如果你正在实现一个赋值运算符(就像一个现有对象的副本),目标对象的引用计数应该保持原样。

于 2012-09-14T16:47:10.807 回答
1

构造函数不安全,因为初始化列表不是原子的(我在标准中没有找到对此的任何引用,但无论如何它都很难实现)。

因此,如果另一个线程将删除当前线程当前复制的对象-就在初始化列表执行和InterlockedIncrement()执行之间-您将收到损坏的(已删除的m_datam_datam_count. 这至少会导致m_data.

放置InterlockedIncrement到初始化器列表将无济于事,因为线程切换可能发生在 ctor 调用之后但在m_count初始化之前。

我不确定是否可以在没有外部锁(互斥锁或临界区)的情况下使其线程安全。您至少可以检查 ctor 中的计数器并在它为零时抛出异常/创建“无效”对象,但这不是好的设计,我不推荐它。

于 2012-09-14T17:02:18.603 回答
0

只要调用代码确保通过引用传递的对象在此函数执行期间不被破坏,则此代码是安全的。这对于任何需要引用的函数都是一样的,你必须非常努力才能不这样做。

析构函数是安全的,因为原子减量保证在一个且只有一个线程中为零。如果是,则必须是所有其他线程都已经完成了对该对象的使用并且已经调用了它自己的递减操作。

这假设您的联锁操作都有完整的障碍。

当count == 1时,我怎么能进入thread1正在复制而thread2同时销毁的场景。线程之间必须进行握手,这意味着thread1必须在thread2的对象超出范围之前完全复制对象正确?

你不能,只要每个线程都有自己对对象的引用。只要 thread1 对对象有自己的引用,就不能在 thread1 复制时销毁该对象。Thread1 不需要在 thread2 的引用消失之前复制,因为除非你有一个对它的引用,否则你永远不会触摸一个对象。

单个引用的线程安全性较弱,不应在不同线程中同时访问。如果两个线程想要访问同一个对象,它们都应该有自己的引用。将对象引用到其他代码(可能在另一个线程中)时,请遵循以下操作序列:

  1. 有自己的参考。

  2. 从您自己的参考中创建其他代码的参考。

  3. 现在您可以销毁您的参考资料或放弃其他参考资料。

于 2012-09-14T18:52:53.070 回答
-1

您的复制构造函数不安全,也不能保证安全。

但是如果你从不使用 new/delete,你可以安全地使用你的类,而只使用自动创建和销毁的对象(按范围)。

于 2012-09-14T20:21:02.657 回答