2 // ConcurrentSkipList.cs
4 // Copyright (c) 2008 Jérémie "Garuma" Laval
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to deal
8 // in the Software without restriction, including without limitation the rights
9 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 // copies of the Software, and to permit persons to whom the Software is
11 // furnished to do so, subject to the following conditions:
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27 using System.Threading;
28 using System.Collections;
29 using System.Collections.Generic;
31 namespace System.Collections.Concurrent
34 internal class ConcurrentSkipList<T> : IProducerConsumerCollection<T>
36 // Used for randomSeed
39 // Used in FindNodes and thus most others methods
40 // avoid heavy local array creation at each method call and use
41 // for thread locallity ThreadStatic attribute
42 [ThreadStaticAttribute]
44 [ThreadStaticAttribute]
51 public readonly int Key;
53 public readonly int TopLayer;
54 public readonly Node[] Nexts;
55 public volatile bool Marked;
56 public volatile bool FullyLinked;
57 public readonly SpinLock SpinLock;
59 public Node (int key, T value, int heightValue)
63 TopLayer = heightValue;
64 Nexts = new Node [heightValue + 1];
65 SpinLock = new SpinLock (false);
66 Marked = FullyLinked = false;
73 const int MaxHeight = 200;
78 public ConcurrentSkipList () : this ((value) => value.GetHashCode ())
82 public ConcurrentSkipList (IEqualityComparer<T> comparer)
83 : this ((value) => comparer.GetHashCode (value))
87 public ConcurrentSkipList(Func<T, int> hasher)
96 succs = new Node [MaxHeight];
98 preds = new Node [MaxHeight];
100 leftSentinel = new Node (int.MinValue, default (T), MaxHeight);
101 rightSentinel = new Node (int.MaxValue, default (T), MaxHeight);
103 for (int i = 0; i < MaxHeight; i++) {
104 leftSentinel.Nexts [i] = rightSentinel;
106 // The or ensures that randomSeed != 0
107 randomSeed = ((uint)Math.Abs (Next())) | 0x0100;
110 public bool TryAdd (T value)
113 throw new ArgumentNullException ("value");
116 int topLayer = GetRandomLevel ();
118 int v = GetKey (value);
121 int found = FindNode (v, preds, succs);
123 // A node with the same key already exists
124 Node nodeFound = succs [found];
125 if (!nodeFound.Marked) {
126 SpinWait sw = new SpinWait ();
127 while (!nodeFound.FullyLinked) {
134 int highestLocked = -1;
136 bool valid = LockNodes (topLayer, ref highestLocked,
137 (layer, pred, succ) => !pred.Marked && !succ.Marked && pred.Nexts [layer] == succ);
141 Node newNode = new Node (v, value, topLayer);
142 for (int layer = 0; layer <= topLayer; layer++) {
143 newNode.Nexts [layer] = succs [layer];
144 preds [layer].Nexts [layer] = newNode;
146 newNode.FullyLinked = true;
148 Unlock (preds, highestLocked);
150 Interlocked.Increment (ref count);
155 bool IProducerConsumerCollection<T>.TryTake (out T value)
157 throw new NotSupportedException ();
160 public T[] ToArray ()
162 int countSnapshot = count;
163 T[] temp = new T [countSnapshot];
170 public void CopyTo (T[] array, int startIndex)
172 IEnumerator<T> e = GetInternalEnumerator ();
173 for (int i = startIndex; i < array.Length; i++) {
176 array [i] = e.Current;
181 void ICollection.CopyTo (Array array, int startIndex)
183 T[] temp = array as T[];
187 CopyTo (temp, startIndex);
190 object ICollection.SyncRoot {
196 bool ICollection.IsSynchronized {
202 public bool Remove (T value)
205 throw new ArgumentNullException ("value");
208 Node toDelete = null;
209 bool isMarked = false;
211 int v = GetKey (value);
214 int found = FindNode (v, preds, succs);
216 if (isMarked || (found != -1 && OkToDelete (succs [found], found))) {
217 // If not marked then logically delete the node
219 toDelete = succs [found];
220 topLayer = toDelete.TopLayer;
223 toDelete.SpinLock.Enter (ref taken);
225 // Now that we have the lock, check if the node hasn't already been marked
226 if (toDelete.Marked) {
227 toDelete.SpinLock.Exit (true);
230 toDelete.Marked = true;
233 int highestLocked = -1;
235 bool valid = LockNodes (topLayer, ref highestLocked,
236 (layer, pred, succ) => !pred.Marked && pred.Nexts [layer] == succ);
240 for (int layer = topLayer; layer >= 0; layer--) {
241 preds [layer].Nexts [layer] = toDelete.Nexts [layer];
243 toDelete.SpinLock.Exit (true);
245 Unlock (preds, highestLocked);
247 Interlocked.Decrement (ref count);
255 public bool Contains (T value)
258 throw new ArgumentNullException ("value");
260 return ContainsFromHash (GetKey (value));
263 internal bool ContainsFromHash (int hash)
266 int found = FindNode (hash, preds, succs);
267 return found != -1 && succs [found].FullyLinked && !succs [found].Marked;
270 internal bool GetFromHash (int hash, out T value)
274 // We are blindly supposing that the hash is correct
275 // i.e. I trust myself :-)
276 int found = FindNode (hash, preds, succs);
283 succs [found].SpinLock.Enter (ref taken);
285 Node node = succs [found];
286 if (node.FullyLinked && !node.Marked) {
291 succs [found].SpinLock.Exit (true);
303 IEnumerator<T> IEnumerable<T>.GetEnumerator ()
305 return GetInternalEnumerator ();
308 IEnumerator IEnumerable.GetEnumerator ()
310 return GetInternalEnumerator ();
313 IEnumerator<T> GetInternalEnumerator ()
315 Node curr = leftSentinel;
316 while ((curr = curr.Nexts [0]) != rightSentinel) {
317 // If there is an Add operation ongoing we wait a little
318 // Possible optimization : use a helping scheme
319 SpinWait sw = new SpinWait ();
320 while (!curr.FullyLinked) {
323 yield return curr.Value;
327 void Unlock(Node[] preds, int highestLocked)
329 for (int i = 0; i <= highestLocked; i++) {
330 preds [i].SpinLock.Exit (true);
334 bool LockNodes (int topLayer, ref int highestLocked, Func<int, Node, Node, bool> validityTest)
336 Node pred, succ, prevPred = null;
339 for (int layer = 0; valid && (layer <= topLayer); layer++) {
340 pred = preds [layer];
341 succ = succs [layer];
342 if (pred != prevPred) {
343 // Possible optimization : limit topLayer to the first refused lock
346 pred.SpinLock.Enter (ref taken);
348 highestLocked = layer;
351 valid = validityTest (layer, pred, succ);
357 int FindNode (int v, Node[] preds, Node[] succs)
359 // With preds and succs we record the path we use for searching v
360 if (preds.Length != MaxHeight || succs.Length != MaxHeight)
361 throw new Exception ("precs or succs don't have the good length");
364 Node pred = leftSentinel;
366 // We start at the higher layer
367 for (int layer = MaxHeight - 1; layer >= 0; layer--) {
368 Node curr = pred.Nexts [layer];
369 // In the current layer we find the best position, then the operation will continue on the
370 // layer just beneath
371 while (v > curr.Key) {
373 curr = curr.Nexts [layer];
375 if (found == -1 && v == curr.Key)
377 preds [layer] = pred;
378 succs [layer] = curr;
384 bool OkToDelete (Node candidate, int found)
386 return candidate.FullyLinked && candidate.TopLayer == found && !candidate.Marked;
389 // Taken from Doug Lea's code released in the public domain
390 int GetRandomLevel ()
397 if ((x & 0x80000001) != 0) // test highest and lowest bits
400 while (((x >>= 1) & 1) != 0) ++level;
407 succs = new Node [MaxHeight];
409 preds = new Node [MaxHeight];
411 // Hopefully these are more optimized than a bare for loop
412 // (I suppose it uses memset internally)
413 Array.Clear (preds, 0, preds.Length);
414 Array.Clear (succs, 0, succs.Length);