using System; using System.Diagnostics; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; #pragma warning disable 0420 /// <title>Lock-free and wait-free generic concurrent dictionary</title> /// <author>Glenn Slayden http://www.glennslayden.com</author> /// <date>December 2010</date> /// <keywords>lock-free, wait-free, non-blocking, concurrent, associative array, dictionary, hash map, hash table</keywords> /// <typeparam name="K">Type of the key that is used to retrieve a data item. All key instances must be distinct. For reference /// type (object) keys and <seealso cref="Nullable<T>"/> keys, key instances may not be null. Value type key types should /// support a low-collision <seealso cref="GetHashCode"/> implementation.</typeparam> /// <typeparam name="V">Type of the data item that is associated with a key.</typeparam> public partial class LockFreeDictionary<K, V> : IDictionary<K, V> where K : IEquatable<K> where V : IEquatable<V> { public LockFreeDictionary() { f_null_check = !EntryBlock.Entry.f_value_type_key || Nullable.GetUnderlyingType(EntryBlock.Entry.t_key) != null; m_config = new Config(this); } [Flags] public enum Options { None = 0, /// Atomic update of dictionary values can be costly, but for some value types, equality comparisons are also costly. /// Setting this flag elects that, when updating, value instances should be checked for equality against the existing /// value for the key (if any) in order to possibly avoid transacting an atomic update. CheckForVacuousValueUpdates = 0x00000001, /// Entries are held in blocks, each of which is pre-allocated for a fixed number of entries. This fixed number of entries /// per block can be calculated based on the number of entries that will cause the block to be allocated in the large object /// heap. This calculation is approximate since it is based on both: implementation details of the GC, and; the marshaling /// sizes--calculated when the dictionary is initialized--of the generic type arguments. AttemptScaleToLargeObjectHeap = 0x00000002, /// Enable the bucket table to be shrunk when the number of elements in the dictionary falls below half of what it /// once was. This does not affect the size of the entry tables, which cannot be efficiently shrunk. ShrinkBucketTable = 0x00000004, }; /// <summary> /// updating of value types can have 3 options: /// - unsafe /// - non-atomic readback-after-write with full fence (but torn read is still possible, and can match the readback) /// - atomic /// </summary> internal Options m_options = Options.AttemptScaleToLargeObjectHeap; public Int64 m_count = 0; public Int64 m_resize_counts; readonly bool f_null_check; public volatile Config m_config; V DefaultValue = default(V); enum Op { Remove, Get, Update, }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Atomic operations /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public V GetOrAdd(K key, V value) { throw new NotImplementedException(); } public bool TryAdd(K key, V value) { return _try_add(key, value); } public bool TryGetValue(K key, out V value) { return _try_get_remove_update(Op.Get, key, ref DefaultValue, out value); } public bool TryRemove(K key, out V value) { return _try_get_remove_update(Op.Remove, key, ref DefaultValue, out value); } public bool TryUpdate(K key, V newValue, V comparisonValue) { return _try_update_atomic(key, ref newValue, ref comparisonValue); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // IDictionary<TKey, TValue> /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Although this member is thread-safe, the value should not be considered added if another thread could have removed /// it during the time this function is returning. /// </summary> /// <param name="key"></param> /// <param name="value"></param> /// <exception cref="ArgumentException">An element with the same key already exists.</exception> /// <exception cref="ArgumentNullException"><paramref name="key"/> cannot be null.</exception> public void Add(K key, V value) { if (!_try_add(key, value)) throw new ArgumentException("An item with the same key has already been added."); } /// <summary> /// Although this member is thread-safe, the result should be considered advisory if another thread could have re-inserted /// an item with the same key by the time the return value is examined. /// </summary> public bool Remove(K key) { V _dont_need; return _try_get_remove_update(Op.Remove, key, ref DefaultValue, out _dont_need); } public V this[K key] { get { V v; if (!_try_get_remove_update(Op.Get, key, ref DefaultValue, out v)) throw new KeyNotFoundException(); return v; } set { V _dont_need; if (!_try_get_remove_update(Op.Update, key, ref value, out _dont_need)) throw new KeyNotFoundException(); } } /// <summary> /// Although this member is thread-safe, the result should be considered advisory if another thread could /// have removed the item by the time the return value is examined. /// </summary> public bool ContainsKey(K key) { V _dont_need; return _try_get_remove_update(Op.Get, key, ref DefaultValue, out _dont_need); } public ICollection<K> Keys { get { throw new NotImplementedException(); } } public ICollection<V> Values { get { throw new NotImplementedException(); } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // ICollection<KeyValuePair<TKey,TValue>> follows /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /// <summary> /// Although this property is thread-safe, the result should be considered advisory if another thread could have added or /// removed items by the time the return value is examined. /// </summary> public int Count { get { Config.BucketListResize bhs = m_config.m_buckets as Config.BucketListResize; if (bhs != null) bhs._check_assist(); return m_config.m_count; } } /// <summary> /// Although this member is thread-safe, the item should not be considered added if another thread could have removed /// it during the time this function is returning. /// </summary> /// <param name="item"></param> void ICollection<KeyValuePair<K, V>>.Add(KeyValuePair<K, V> item) { if (!_try_add(item.Key, item.Value)) throw new ArgumentException("An item with the same key has already been added."); } /// <summary> /// Although this member is thread-safe, the result should be considered advisory if another thread could have re-inserted /// an item with the same key by the time the return value is examined. /// </summary> /// <param name="item"></param> /// <returns></returns> bool ICollection<KeyValuePair<K, V>>.Remove(KeyValuePair<K, V> item) { V _dont_need; return _try_get_remove_update(Op.Remove, item.Key, ref DefaultValue, out _dont_need); } /// <summary> /// Although this member is thread-safe, the dictionary should not be considered empty if another thread could have /// added items to the dictionary during the time this function is returning. Changes that were initiated prior to /// this call but which haven't completed yet may be applied to either before or after the clearing operation. /// </summary> public void Clear() { Config.BucketListResize bhs = m_config.m_buckets as Config.BucketListResize; if (bhs != null) bhs._check_assist(); m_config = new Config(this); } /// <summary> /// Although this member is thread-safe, the result should be considered advisory if another thread could /// have removed the item by the time the return value is examined. /// </summary> /// <param name="item"></param> /// <returns></returns> bool ICollection<KeyValuePair<K, V>>.Contains(KeyValuePair<K, V> item) { V v; return _try_get_remove_update(Op.Get, item.Key, ref DefaultValue, out v) && v.Equals(item.Value); } /// <summary> /// Copy a snapshot of the dictionary contents to an array of key-value pairs. /// </summary> /// <param name="array"></param> /// <param name="arrayIndex"></param> void ICollection<KeyValuePair<K, V>>.CopyTo(KeyValuePair<K, V>[] array, int arrayIndex) { throw new NotImplementedException(); } /// <summary> /// This property has the value 'true' /// </summary> bool ICollection<KeyValuePair<K, V>>.IsReadOnly { get { return true; } } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // IEnumerable<KeyValuePair<TKey,TValue>> follows /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// public IEnumerator<KeyValuePair<K, V>> GetEnumerator() { throw new NotImplementedException(); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // IEnumerable follows /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } };