写时复制 (COW) 惯用法的线程安全实现?
任何人都可以向我指出 Copy-on-write (COW)< 的线程安全实现/a> 习语? 此网站上的示例代码看起来不错 - 是它是线程安全的吗?
如果有人想知道我将用它做什么:我有一个 Foo
类,它有一个 std::map
成员。 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
对象的构造函数或赋值运算符。但也许有人可以发现线程安全错误?
Can anyone point me to a thread-safe implementation of the Copy-on-write (COW) idiom? The sample code on this site looks good -- is it thread-safe?
In case anyone is wondering what I will be using it for: I have a Foo
class that has a std::map<int,double>
member. Foo
objects are copied very frequently in my code, but the copies rarely modify the contained map
. I found that COW gives me a 22% performance boost, compared to copying the whole map contents in the Foo
copy constructor, but my COW implementation crashes when multiple threads are used.
UPDATE:
Okay, here is the code, reduced to a minimal example, since you asked for it:
First, a reference-counting map:
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_;
};
And here is the class Foo
that contains such a map RcMap
, using a Copy-on-write mechanism:
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_;
};
I haven't yet been able to reproduce the crash using this minimal example, but in my original code it happens when I use the copy constructor or assignment operator of Foo
objects in parallel. But maybe someone can spot the thread-safety bug?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
COW 本质上是线程安全的,因为原始版本本质上是不可变的,并且只有引发副本的线程才能在创建过程中看到复制的版本。您只需要注意两件事:
COW is inherently thread-safe, since the original is essentially immutable, and only the thread that induces the copy sees the copied version in the process of being created. You only need to watch for two things:
RcMap
的引用计数需要原子化才能保证线程安全。在 G++ 4.1 中,您可以使用原子内置函数 来实现这一点。RcMap
's reference counts need to be made atomic in order to be thread safe. In G++ 4.1, you an use the atomic builtins to implement this.如果您要复制可变映射(看起来像这样),那么在复制完成之前不要减少原始对象的引用计数。 (因为否则您可能最终会允许写入正在复制的对象,从而破坏线程安全。)
更好的是,如果可以的话,使用完全不可变的映射实现(通过使用共享子结构使复制和更新更加便宜)。关于此主题的上一个问题目前尚未得到解答。
If you're copying a mutable map (it looks like you are), then don't decrease the reference count on the original object until after the copy is complete. (Because otherwise you may wind up allowing writes to the object you're copying, thereby breaking thread safety.)
Better yet, use a fully immutable map implementation (that makes copies and updates even cheaper by using shared substructure) if you can. There's a previous question on this topic that's currently unanswered.