C++ 多读/单写器类的线程安全

C++ 多读/单写器类的线程安全,c++,boost,thread-safety,C++,Boost,Thread Safety,我正在制作一套经常阅读但很少写作的作品 class A { boost::shared_ptr<std::set<int> > _mySet; public: void add(int v) { boost::shared_ptr<std::set<int> > tmpSet(new std::set<int>(*_mySet)); tmpSet->insert(v); // insert to tmpS

我正在制作一套经常阅读但很少写作的作品

class A {
  boost::shared_ptr<std::set<int> > _mySet;
public:
  void add(int v) {
    boost::shared_ptr<std::set<int> > tmpSet(new std::set<int>(*_mySet));
    tmpSet->insert(v);  // insert to tmpSet
    _mySet = tmpSet;    // swap _mySet
  }
  void check(int v) {
    boost::shared_ptr<std::set<int> > theSet = _mySet;
    if (theSet->find(v) != theSet->end()) {
      // do something irrelevant
    }
  }
};
A类{
boost::shared_ptr_mySet;
公众:
无效添加(int v){
boost::shared_ptr tmpSet(新std::set(*u mySet));
tmpSet->insert(v);//插入到tmpSet
_mySet=tmpSet;//swap\u mySet
}
无效检查(INTV){
boost::shared\u ptr theSet=\u mySet;
如果(theSet->find(v)!=theSet->end()){
//做些无关紧要的事
}
}
};

在类中,
add()
仅由一个线程调用,
check()
由多个线程调用
check()
不关心
\u mySet
是否是最新的。类线程安全吗?执行
check()
的线程是否可能在
insert to tmpSet
之前观察到
swap\u mySet
发生?

您确实需要同步,这不是线程安全的。一般来说,这并不重要,即使是像
shared+=value这样简单的东西不是线程安全的

请看下面有关共享线程安全的示例:

我也会质疑您在
add()
中的分配/交换以及在
check()中使用
shared\u ptr

更新:


我回去重新放了dox用于共享\u ptr。。。它很可能是线程安全的,因为shared_ptr的引用计数是线程安全的。但是,如果不使用读/写锁,则会造成(IMHO)不必要的复杂性。

这是一个有趣的使用
shared\u ptr
实现线程安全的方法。 是否正常取决于螺纹的安全保证
boost::shared_ptr
。特别是,它是否建立了某种 fence或membar,这样可以保证所有写入
set
的构造函数和
insert
函数出现在任何 指针值的修改变得可见

我在Boost中找不到任何线程安全保证 智能指针的文档。这让我惊讶,因为我确信 有一些。但是快速查看一下1.47.0的源代码就发现没有, 并且在线程化环境中使用
boost::shared_ptr
将 失败。(有人能告诉我我错过了什么吗 相信
boost::shared_ptr
忽略了线程。)

无论如何,有三种可能性:您不能使用共享指针 在线程化环境中(似乎是这样),共享 指针在线程化环境中确保其自身的内部一致性, 但是没有建立关于其他对象的顺序,或者 共享指针建立完整的顺序。只有在最后一种情况下才会发生 你的代码应该是安全的。在第一种情况下,您需要某种形式的 把所有东西都锁起来,第二,你需要一些 围栏或membar,以确保实际完成必要的写入 在发布新版本之前,他们将在
尝试读取它。

最终,此代码应该是线程安全的:

atomic_存储(&u my_set,tmpSet)

theSet=原子负载(&u mySet)

(而不是简单的作业)

但我不知道目前对共享ptr的原子性支持的现状

请注意,以无锁的方式将原子性添加到共享的\u ptr中确实很困难;因此,即使实现了原子性,它也可能依赖于互斥锁或用户模式自旋锁,因此,有时可能会遇到性能问题


编辑:也许还应该添加_my_set成员变量的volatile限定符。。但是我不确定原子操作的语义是否严格要求它

我认为如果共享ptr的交换是原子的,那么这将是线程安全的。否则,可以在“添加”的末尾进行部分交换,在“检查”的开头进行部分复制。你可以用一个真正的原子指针类型的类来替换shared_ptr,然后这个算法就正确了。我理解为什么
shared+=value
不是线程安全的,因为它是一个读-修改-写操作。但是,指针的复制和交换是非线程安全的,因为指针引用的对象可能处于不一致的状态或其他状态吗?check()中的共享_ptr确保find()和end()应用于同一对象。有什么问题吗?@Anycorn:引用计数本身是线程安全的,但是复制、移动或分配共享\u ptr与引用计数不同步,因此这些操作在“volatile”共享指针上不是原子操作。您最初的答案似乎是正确的,但更新是错误的:)显然,最近的boost库支持共享ptr上的原子加载/原子存储操作,但它们的实现似乎使用了自旋锁,从性能角度来看,这是一个有争议的选择(另请参见我的答案)。。嗯,我没有看到原子加载/存储的易失性变体,我不明白为什么。。它们显然使用自旋锁,但不提供易失性变量。@user396672要获得锁定,您必须使用
原子存储
原子加载
来访问
共享ptr
。否则,绝对没有保护:
操作符=
基于
交换
,而
交换
独立交换两个指针。(事实上,这是经典的保证:在原始代码中,一个线程修改智能指针,而其他线程访问它,因此需要某种外部同步。)你是对的,所有访问操作都应该是原子的(在我的回答中,我忘了set-ctor中使用的u*_mySet参数,参数应该是*atomic_-load(_-mySet)我不希望在智能指针上定义
原子存储
原子加载
。该标准要求实现整数类型和(原始)指针,而不是其他。除非我忽略了某些东西(很可能),否则没有类类型(没有