我试图围绕如何确保对象引用计数是线程安全的。
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(共享对象); //按值传递的共享对象,递增计数等等。
ThreadB //接受对象,将其添加到共享对象列表中。 ThreadB位于一个计时器上,通知所有SharedObjects事件。在通知之前,列表的副本受到关键部分的保护。 CS被释放,通知副本。
的ThreadA 分离(共享对象); //从对象列表中删除共享对象
现在,同时ThreadB正在使用SharedOjbect,并且在ThreadA分离所述共享对象之前已经创建了该列表的副本。一切都好吗?
答案 0 :(得分:2)
从技术上讲,它应该是安全的。
为什么呢?因为为了复制一个对象,源需要有一个“引用”,因此在复制期间它不会消失。此外,没有人访问当前正在构建的对象。
析构函数也是安全的,因为无论如何都没有引用。
但您可能想重新考虑复制引用计数。那些引用实际上并不存在;只要在复制之前获得原始引用,每个引用原始文件的人都会以某种方式减少复制的引用计数。副本应该像新对象一样启动,引用计数为1。
编辑:同样,如果您正在实现一个赋值运算符(它类似于现有对象的副本),则目标对象的引用计数应保持不变。
答案 1 :(得分:1)
构造函数不安全,因为初始化列表不是原子的(我没有在Standard中找到任何对此的引用,但无论如何都很难实现。)
因此,如果另一个线程将删除当前线程当前复制的对象 - 在初始化列表执行和InterlockedIncrement()
执行之间 - 您将收到损坏的(已删除的m_data
)m_data
和m_count
。这至少会导致m_data
的双重删除。
将InterlockedIncrement
放置到初始化列表中无济于事,因为在ctor调用之后但在m_count
初始化之前可以进行线程切换。
我不确定是否可以在没有外部锁定(互斥或关键部分)的情况下使其保持线程安全。您至少可以检查ctor中的计数器并抛出异常/创建“无效”对象(如果它为零),但这不是好设计,我不推荐它。
答案 2 :(得分:0)
只要调用代码确保在执行此函数期间不会销毁通过引用传递的对象,此代码是安全的。这对于任何需要引用的函数都是一样的,你必须非常努力地不这样做。
析构函数是安全的,因为原子减量在一个且只有一个线程中保证为零。如果是,则必须是每个其他线程已经完成使用该对象并且已经调用了自己的减量操作。
这假设您的互锁操作都有完全障碍。
我怎么能进入thread1正在复制并且thread2在count == 1时同时销毁的场景。线程之间必须有握手,这意味着thread1必须在thread2的对象离开之前完全复制对象。范围是否正确?
你不能,只要每个线程都有自己的对象引用。只要thread1有自己对象的引用,就不能在thread1复制时销毁对象。在thread2的引用消失之前,Thread1不需要复制,因为除非你引用了对象,否则永远不会触摸对象。
单个引用具有较弱的线程安全性,不应同时在不同的线程中访问。如果两个线程想要访问同一个对象,则每个线程都应该有自己的引用。当将对象的引用提供给其他代码(可能在另一个线程中)时,请遵循以下操作序列:
有自己的参考。
从您自己的参考资料中为其他代码创建参考。
现在您可以销毁您的参考文献或放弃其他参考文献。
答案 3 :(得分:-1)
您的复制构造函数不安全,无法安全。
但如果您从不使用new / delete,则可以安全地使用您的类,但只能使用自动创建和销毁的对象(按范围)。