C#中的ReaderWriterLock和LockFree Data Structure
前一阵在一个project中使用了ReaderWriterLock,发现了两个问题:
后来我干脆不用ReaderWriterLock了,直接换成了LockFree的方法。这个得益于ibm的一些paper。(http://www.research.ibm.com/people/m/michael/pubs.htm )
在C#中实现LockFree其实是很简单的,因为有了Garbage Collection,
code:
解释如下:
这种方法的好处是在Lookup的时候没有任何lock,从而极大的提高了performance。(我的project里面比ReaderWriterLock提高了2000倍,)
对LockFree有研究的或者有兴趣的可以留言大家讨论讨论,:)
- Performance非常差
- UpgradeToWriterLock并不是atomic的从ReaderLock转换到WriterLock,而是等同于"lock.ReleaseReaderLock(); lock.AcquireWriterLock();"。这样的semantics有一定的迷惑性,我开始的时候也认为这个operation是atomic的,等出现bug并debug了很久才发现原来如此。不过经过认真的思考,发现这其实不是.NET designer的错,根本没办法把这个operation设计成atomic的。原因如下:
- 很多个thread同时acquire到了ReaderLock,
- 他们都call UpgradeToWriterLock,如果这个operation是atomic的,那么没有哪个thread能upgrade成功。
后来我干脆不用ReaderWriterLock了,直接换成了LockFree的方法。这个得益于ibm的一些paper。(http://www.research.ibm.com/people/m/michael/pubs.htm )
在C#中实现LockFree其实是很简单的,因为有了Garbage Collection,
code:
1 class LockFreeDictionary<Key, Value>
2 {
3 private Dictionary<Key, Value> m_dict = new Dictionary<Key, Value>();
4
5 public Value Lookup(Key key)
6 {
7 return m_dict[key];
8 }
9
10 public void Update(Key key, Value value)
11 {
12 Dictionary<Key, Value> newDict = null;
13 Dictionary<Key, Value> oldDict = null;
14 do
15 {
16 oldDict = m_dict;
17 newDict = new Dictionary<Key, Value>(oldDict);
18 newDict[key] = value;
19 } while (Interlocked.CompareExchange<Dictionary<Key, Value>>(ref m_dict, newDict, oldDict) != oldDict);
20 }
21 }
22
2 {
3 private Dictionary<Key, Value> m_dict = new Dictionary<Key, Value>();
4
5 public Value Lookup(Key key)
6 {
7 return m_dict[key];
8 }
9
10 public void Update(Key key, Value value)
11 {
12 Dictionary<Key, Value> newDict = null;
13 Dictionary<Key, Value> oldDict = null;
14 do
15 {
16 oldDict = m_dict;
17 newDict = new Dictionary<Key, Value>(oldDict);
18 newDict[key] = value;
19 } while (Interlocked.CompareExchange<Dictionary<Key, Value>>(ref m_dict, newDict, oldDict) != oldDict);
20 }
21 }
22
解释如下:
- line 16, keep a reference to the original Dictionary object,
- line 17, construct a new Dictionary object base on original object. For oldDict, this step is readonly, and doesn't need lock,
- line 18, perform the update operation upon the new constructed object,
- line 19, try to swap the new object into the original one. If the return value of Interlocked.CompareExchange operation is NOT equal to oldDict, it means during this do-while block executation, there is another thread changed m_dict. In this scenario, we need to do the update again.
- the swapped out object (oldDict) can be collected by Garbage Collection.
- if we want to use LockFree data structure in C++, there is another technique called Hazard Pointer. it's in the ibm research papers.
这种方法的好处是在Lookup的时候没有任何lock,从而极大的提高了performance。(我的project里面比ReaderWriterLock提高了2000倍,)
对LockFree有研究的或者有兴趣的可以留言大家讨论讨论,:)