1 // ConcurrentSkipList.cs
3 // Copyright (c) 2008 Jérémie "Garuma" Laval
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // 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;
30 using System.Collections.Concurrent;
32 namespace Mono.Collections.Concurrent
34 public class ConcurrentSkipList<T> : ICollection<T>, IEnumerable<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
43 static Node[] precedents;
45 static Node[] succedings;
47 static bool[] takenLocks;
53 public readonly int Key;
55 public readonly int TopLayer;
56 public readonly Node[] Nexts;
57 public volatile bool Marked;
58 public volatile bool FullyLinked;
61 public Node (int key, T value, int heightValue)
65 TopLayer = heightValue;
66 Nexts = new Node [heightValue + 1];
67 Lock = new SpinLock (true);
68 Marked = FullyLinked = false;
75 const int MaxHeight = 200;
78 IEqualityComparer<T> comparer;
80 public ConcurrentSkipList () : this (EqualityComparer<T>.Default)
85 public ConcurrentSkipList (IEqualityComparer<T> comparer)
88 throw new ArgumentNullException ("comparer");
90 this.comparer = comparer;
96 var left = new Node (int.MinValue, default (T), MaxHeight);
97 var right = new Node (int.MaxValue, default (T), MaxHeight);
99 for (int i = 0; i < MaxHeight; i++) {
100 left.Nexts [i] = right;
102 // The or ensures that randomSeed != 0
103 randomSeed = ((uint)System.Math.Abs (Next())) | 0x0100;
106 rightSentinel = right;
109 public bool TryAdd (T value)
112 throw new ArgumentNullException ("value");
115 int topLayer = GetRandomLevel ();
117 int v = comparer.GetHashCode (value);
120 int found = FindNode (v, precedents, succedings);
122 // A node with the same key already exists
123 Node nodeFound = succedings [found];
124 if (!nodeFound.Marked) {
125 SpinWait sw = new SpinWait ();
126 while (!nodeFound.FullyLinked)
133 int highestLocked = -1;
135 bool valid = LockNodes (topLayer, ref highestLocked, precedents, succedings,
136 (layer, pred, succ) => !pred.Marked && !succ.Marked && pred.Nexts [layer] == succ);
140 Node newNode = new Node (v, value, topLayer);
141 for (int layer = 0; layer <= topLayer; layer++) {
142 newNode.Nexts [layer] = succedings [layer];
143 precedents [layer].Nexts [layer] = newNode;
145 newNode.FullyLinked = true;
147 Unlock (precedents, takenLocks, highestLocked);
149 Interlocked.Increment (ref count);
154 void ICollection<T>.Add (T item)
159 public T[] ToArray ()
161 int countSnapshot = count;
162 T[] temp = new T [countSnapshot];
169 public void CopyTo (T[] array, int startIndex)
172 throw new ArgumentNullException ("array");
174 throw new ArgumentOutOfRangeException ("startIndex");
175 if (count > array.Length - startIndex)
176 throw new ArgumentException ("array", "The number of elements is greater than the available space from startIndex to the end of the destination array.");
178 IEnumerator<T> e = GetInternalEnumerator ();
179 for (int i = startIndex; i < array.Length; i++) {
182 array [i] = e.Current;
187 public bool Remove (T value)
190 throw new ArgumentNullException ("value");
193 Node toDelete = null;
194 bool isMarked = false;
196 int v = comparer.GetHashCode (value);
199 int found = FindNode (v, precedents, succedings);
201 int highestLocked = -1;
203 if (isMarked || (found != -1 && OkToDelete (succedings [found], found))) {
204 // If not marked then logically delete the node
207 toDelete = succedings [found];
208 topLayer = toDelete.TopLayer;
210 toDelete.Lock.Enter (ref taken);
211 // Now that we have the lock, check if the node hasn't already been marked
215 toDelete.Marked = true;
219 bool valid = LockNodes (topLayer, ref highestLocked, precedents, succedings,
220 (layer, pred, succ) => !pred.Marked && pred.Nexts [layer] == succ);
224 for (int layer = topLayer; layer >= 0; layer--)
225 precedents [layer].Nexts [layer] = toDelete.Nexts [layer];
228 toDelete.Lock.Exit ();
229 Unlock (precedents, takenLocks, highestLocked);
232 Interlocked.Decrement (ref count);
240 public bool Contains (T value)
243 throw new ArgumentNullException ("value");
245 return ContainsHash (comparer.GetHashCode (value));
248 public bool ContainsHash (int hash)
251 int found = FindNode (hash, precedents, succedings);
252 return found != -1 && succedings [found].FullyLinked && !succedings [found].Marked;
255 public bool TryGetFromHash (int hash, out T value)
259 // We are blindly supposing that the hash is correct
260 // i.e. I trust myself :-)
261 int found = FindNode (hash, precedents, succedings);
266 Node node = succedings [found];
269 node.Lock.Enter (ref taken);
271 if (node.FullyLinked && !node.Marked) {
294 IEnumerator<T> IEnumerable<T>.GetEnumerator ()
296 return GetInternalEnumerator ();
299 IEnumerator IEnumerable.GetEnumerator ()
301 return GetInternalEnumerator ();
304 IEnumerator<T> GetInternalEnumerator ()
306 Node curr = leftSentinel;
307 while ((curr = curr.Nexts [0]) != rightSentinel && curr != null) {
308 // If there is an Add operation ongoing we wait a little
309 // Possible optimization : use a helping scheme
310 SpinWait sw = new SpinWait ();
311 while (!curr.FullyLinked)
314 yield return curr.Value;
318 bool ICollection<T>.IsReadOnly {
324 void Unlock (Node[] preds, bool[] takenLocks, int highestLocked)
326 for (int layer = 0; layer <= highestLocked; layer++)
327 if (takenLocks [layer])
328 preds [layer].Lock.Exit ();
331 bool LockNodes (int topLayer, ref int highestLocked, Node[] preds, Node[] succs, Func<int, Node, Node, bool> validityTest)
333 Node pred, succ, prevPred = null;
336 for (int layer = 0; valid && (layer <= topLayer); layer++) {
337 pred = preds [layer];
338 succ = succs [layer];
339 takenLocks[layer] = false;
341 if (pred != prevPred) {
342 // Possible optimization : limit topLayer to the first refused lock
343 pred.Lock.Enter (ref takenLocks[layer]);
344 highestLocked = layer;
348 valid = validityTest (layer, pred, succ);
354 int FindNode (int v, Node[] preds, Node[] succs)
356 // With preds and succs we record the path we use for searching v
357 if (preds.Length != MaxHeight || succs.Length != MaxHeight)
358 throw new Exception ("preds or succs don't have the good length");
361 Node pred = leftSentinel;
363 // We start at the higher layer
364 for (int layer = MaxHeight - 1; layer >= 0; layer--) {
365 Node curr = pred.Nexts [layer];
366 // In the current layer we find the best position, then the operation will continue on the
367 // layer just beneath
368 while (v > curr.Key) {
370 curr = curr.Nexts [layer];
372 if (found == -1 && v == curr.Key)
374 preds [layer] = pred;
375 succs [layer] = curr;
381 bool OkToDelete (Node candidate, int found)
383 return candidate.FullyLinked && candidate.TopLayer == found && !candidate.Marked;
386 // Taken from Doug Lea's code released in the public domain
387 int GetRandomLevel ()
394 if ((x & 0x80000001) != 0) // test highest and lowest bits
397 while (((x >>= 1) & 1) != 0) ++level;
403 // If one is null, the others too
404 if (succedings == null) {
405 succedings = new Node [MaxHeight];
406 precedents = new Node [MaxHeight];
407 takenLocks = new bool [MaxHeight];
412 // Hopefully these are more optimized than a bare for loop
413 // (I suppose it uses memset internally)
414 Array.Clear (precedents, 0, precedents.Length);
415 Array.Clear (succedings, 0, succedings.Length);
416 Array.Clear (takenLocks, 0, takenLocks.Length);