线上安全实现写时复制(COW)习惯用法?

时间:2010-11-30 00:41:49

标签: c++ multithreading memory idioms

有人能指出我Copy-on-write (COW)成语的线程安全实现吗? this site上的示例代码看起来不错 - 它是否是线程安全的?

如果有人想知道我将使用它:我有一个Foo类,其中有一个std::map<int,double>成员。我的代码中经常复制Foo个对象,但副本很少修改包含的map。我发现,与在Foo复制构造函数中复制整个地图内容相比,COW的性能提升了22%,但是当使用多个线程时,我的COW实现崩溃了。

更新

好的,这是代码,简化为最小的例子,因为你要求它:

首先,引用计数图:

class RcMap {                             
 public:
  typedef std::map<int,double> Container;
  typedef Container::const_iterator const_iterator;
  typedef Container::iterator iterator;

  RcMap() : count_(1) {}

  RcMap(const RcMap& other) : count_(1) {
    m_ = other.Get();
  }

  unsigned Count() const { return count_; }
  unsigned IncCount() { return ++count_; }
  unsigned DecCount() {
    if(count_ > 0) --count_;
    return count_;
  }
  void insert(int i, double d) {
    m_.insert(std::make_pair(i,d));
  }
  iterator begin() { return m_.begin(); }
  iterator end() { return m_.end(); }
  const_iterator begin() const { return m_.begin(); }
  const_iterator end() const { return m_.end(); }

 protected:
  const Container& Get() const { return m_; }

 private:
  void operator=(const RcMap&); // disallow

  Container m_;
  unsigned count_;
};

这是包含这样一个映射Foo的类RcMap,使用写时复制机制:

class Foo {
 public:
  Foo() : m_(NULL) {}

  Foo(const Foo& other) : m_(other.m_) {
    if (m_) m_->IncCount();
  }

  Foo& operator= (const Foo& other) {
    RcMap* const old = m_;
    m_ = other.m_;
    if(m_ != 0)
      m_->IncCount();
    if (old != 0 && old->DecCount() == 0) {
      delete old;
    }
    return *this;
  }

  virtual ~Foo() {
    if(m_ != 0 && m_->DecCount() == 0){
      delete m_;
      m_ = 0;
    }
  }

  const RcMap& GetMap() const {
    if(m_ == 0)
      return EmptyStaticRcMap();
    return *m_;
  }

  RcMap& GetMap() {
    if(m_ == 0)
      m_ = new RcMap();
    if (m_->Count() > 1) {
      RcMap* d = new RcMap(*m_);
      m_->DecCount();
      m_ = d;
    }
    assert(m_->Count() == 1);
    return *m_;
  }

  static const RcMap& EmptyStaticRcMap(){
    static const RcMap empty;
    return empty;
  }

 private:
  RcMap* m_;
};

我还没有能够使用这个最小的例子重现崩溃,但在我的原始代码中,当我同时使用Foo个对象的复制构造函数或赋值运算符时,就会发生这种情况。但也许有人能发现线程安全漏洞?

3 个答案:

答案 0 :(得分:3)

COW本质上是线程安全的,因为原始版本本质上是不可变的,只有引发复制的线程才能在创建过程中看到复制的版本。你只需要注意两件事:

  1. 确保在复制过程中原件不会被其他线程删除。这是一个正交问题(例如,你可以使用线程安全的引用计数)。
  2. 确保复制时执行的所有读取操作都是线程安全的。这很少是一个问题,但有时读取可能会填充缓存。
    • 事实上,如果违反了这个假设,那就是读操作不是线程安全的问题,并且可能会影响代码而不仅仅是COW。

答案 1 :(得分:2)

为了保证线程安全,需要将

RcMap的引用计数设为原子。在G ++ 4.1中,您可以使用atomic builtins来实现此目的。

答案 2 :(得分:1)

如果您正在复制可变地图(看起来像是这样),那么在复制完成之前,请不要减少原始对象的引用计数。 (因为否则你可能最终允许写入你正在复制的对象,从而破坏了线程的安全性。)

更好的是,如果可以的话,使用完全不可变的地图实现(通过使用共享子结构使副本和更新更便宜)。这个主题有previous question,目前尚无人接听。