无锁数据结构(三)

 

无锁数据结构(三)

Andrei Alexandrescu

December 16, 2007

译者:张桂权

12/25/2007


初稿阶段,没有得到许可不得引用,否则后果自负) 

4 一个无锁WRRM Map

列写提供限定首字母缩写词的好处,所以让我们定义WRRM(“少写多读”,“write rarely read many”)的maps作为变化之前进行很多次读取的maps。实例包括对象工厂(object factories)[1],许多观察者设计模式[5]的实例,映射货币名字到兑换率,这些被查找许许多多次,仅被比较慢的流和其他的查找表更新。

WRRM maps可以通过std:map或以前标准中的hash_map来实现,但是作为Modern C++ Design争论的assoc_vector(一个排过序的vector 或 pairs)是WRRM map的最佳候选,因为它能够加快查找速度。不论采用什么结构,我们的无锁切面正交其中。我们将把我们的后端称为Map。同样,我们不在乎map提供的迭代切面;我们把map视为提供查找key或更新key-value对的手段的表。

为了翻新一个“lockful”(基于锁)的实现,我们应该将一个Map对象和一个Mutex对象联合起来:

// WRRMMap的一个有锁实现

template <class K, class V>

class WRRMMap {

Mutex mtx_;

Map map_;

public:

V Lookup(const K& k) {

Lock lock(mtx_);

return map_[k];

}

void Update(const K& k,

const V& v) {

Lock lock(mtx_);

map_.insert(make_pair(k, v));

}

};

像石头一样结实——但是需要代价。每一个查找加锁或解锁Mutex,虽然(1)并行查找不需要连锁(interlock),和(2)通过spec,比起Lookup来说,Update被调用的次数更少。哎哟,现在让我们提供一个更好的实现。

5 垃圾收集器,你在哪儿?

我们的第一个WRRMMap的实现中,没有体现如下的思想:

(1)      读几乎没有锁

(2)   更新产生了整个map的一个副本。更新这个副本,然后试图用旧的map CAS它。当CAS操作不成功时试图在循环中重复尝试copy/update/CAS过程。

(3) 因为CAS在交换的位数上有限制,所以我们把Map作为一个指针存储,而不是直接作为一个WRRMMAP成员。

// WRRMMap的第一个无锁实现

// 仅当你有GC(垃圾收集器)时,才正常工作

template <class K, class V>

class WRRMMap {

Map* pMap_;

public:

V Lookup(const K& k) {

//看好了哦,没锁

return (*pMap_)[k];

}

void Update(const K& k,

const V& v) {

Map* pNew = 0;

do {

Map* pOld = pMap_;

delete pNew;

pNew = new Map(*pOld);

pNew->insert(make_pair(k, v));

} while (!CAS(&pMap_, pOld, pNew));

// 不要delete pMap_;

}

};

这段代码可以正常工作。在一个循环中,Update子例程对map做了一个完整的拷贝,并往其中增加一个新项,然后尝试交换指针。进行CSA非常重要,并且这不是简单的赋值;否则,下列连续事件可能破坏我们的map:

(1)    线程A拷贝这个map;

(2)    线程B拷贝这个map的同时往其中增加一个新项;

(3)     线程A增加另外的一些项;

(4)    线程A用自己的map版本——一个没有包含B增加的项的版本,来替换这个map。

 

有了CAS,就万事亨通了,因为每一个线程都想“假设自从我看它,拷贝它之后,这个map没有发生变化,否则我将从头来过”。

请注意,这里实现了无锁的Update,但是没有上面定义的无等待。如果很多线程并行的调用Update,每一个特定的线程都可能不确定的循环,但是能保证在整个过程中有些线程成功的更新这个结构,这样全局的进度在每一步都有进展。庆幸的是,Lookup是无等待的。

在一个有垃圾回收的环境里,我们算是完成任务了,本文将以升调(upbeat note)结束。没有垃圾回收,但是,这个内容很丰富哦,会很难忍受的(原因之一,你不得不看我的更多文字);这是因为不管愿不愿意我们不可以简单的处置旧的pMap_;否则,当我们正试图delete它时,如果其它的一些线程通过Lookup函数来访问pMap_那将会发生什么事情?你知道,一个垃圾收集器会访问所有线程的数据和私有的堆。当一个pMap_不再使用时,应该有一个很好的透明机制,并进行认真的检查。没有垃圾收集器,事情就变得很困难了。实际上,更加困难了,所以确定性的内存释放是无锁数据结构中一个最基础的问题。


---------------源文档------------------
Lock-Free Data Structures
Andrei Alexandrescu
December 17, 2007
http://erdani.org/publications/cuj-2004-10.pdf
最后一次访问时间:2007年12月30日
----------------------------------------

你可能感兴趣的:(C/C++)