2014-09-05 44 views
1

完全编辑早期版本,以下实现是否可以成为线程安全列表实现。我只需要知道它是否真的会安全或不安全,我知道性能方面仍然存在问题。目前版本使用ReaderWriterLockSlim,我有另一种使用锁的实现,做同样的工作使用读写器锁创建线程安全列表

using System.Collections.Generic;使用System.Threading的 ;

/// <summary> 
/// Thread safe version of the List using ReaderWriterLockSlim 
/// </summary> 
/// <typeparam name="T"></typeparam> 
public class ThreadSafeListWithRWLock<T> : IList<T> 
{ 
    // Internal private list which would be accessed in a thread safe manner 
    private List<T> internalList; 

    // ReaderWriterLockSlim object to take care of thread safe acess between multiple readers and writers 
    private readonly ReaderWriterLockSlim rwLockList; 

    /// <summary> 
    /// Public constructor with variable initialization code 
    /// </summary> 
    public ThreadSafeListWithRWLock() 
    { 
     internalList = new List<T>(); 

     rwLockList = new ReaderWriterLockSlim(); 
    } 

    /// <summary> 
    /// Get the Enumerator to the Thread safe list 
    /// </summary> 
    /// <returns></returns> 
    public IEnumerator<T> GetEnumerator() 
    { 
     return Clone().GetEnumerator(); 
    } 

    /// <summary> 
    /// System.Collections.IEnumerable.GetEnumerator implementation to get the IEnumerator type 
    /// </summary> 
    /// <returns></returns> 
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return Clone().GetEnumerator(); 
    } 

    /// <summary> 
    /// Clone method to create an in memory copy of the Thread safe list 
    /// </summary> 
    /// <returns></returns> 
    public List<T> Clone() 
    { 
     List<T> clonedList = new List<T>(); 

     rwLockList.EnterReadLock(); 

     internalList.ForEach(element => { clonedList.Add(element); });    

     rwLockList.ExitReadLock(); 

     return (clonedList); 
    } 

    /// <summary> 
    /// Add an item to Thread safe list 
    /// </summary> 
    /// <param name="item"></param> 
    public void Add(T item) 
    { 
     rwLockList.EnterWriteLock(); 

     internalList.Add(item); 

     rwLockList.ExitWriteLock(); 
    } 

    /// <summary> 
    /// Remove an item from Thread safe list 
    /// </summary> 
    /// <param name="item"></param> 
    /// <returns></returns> 
    public bool Remove(T item) 
    { 
     bool isRemoved; 

     rwLockList.EnterWriteLock(); 

     isRemoved = internalList.Remove(item); 

     rwLockList.ExitWriteLock(); 

     return (isRemoved); 
    } 

    /// <summary> 
    /// Clear all elements of Thread safe list 
    /// </summary> 
    public void Clear() 
    { 
     rwLockList.EnterWriteLock(); 

     internalList.Clear(); 

     rwLockList.ExitWriteLock(); 
    } 

    /// <summary> 
    /// Contains an item in the Thread safe list 
    /// </summary> 
    /// <param name="item"></param> 
    /// <returns></returns> 
    public bool Contains(T item) 
    { 
     bool containsItem; 

     rwLockList.EnterReadLock(); 

     containsItem = internalList.Contains(item); 

     rwLockList.ExitReadLock(); 

     return (containsItem); 
    } 

    /// <summary> 
    /// Copy elements of the Thread safe list to a compatible array from specified index in the aray 
    /// </summary> 
    /// <param name="array"></param> 
    /// <param name="arrayIndex"></param> 
    public void CopyTo(T[] array, int arrayIndex) 
    { 
     rwLockList.EnterReadLock(); 

     internalList.CopyTo(array,arrayIndex); 

     rwLockList.ExitReadLock(); 
    } 

    /// <summary> 
    /// Count elements in a Thread safe list 
    /// </summary> 
    public int Count 
    { 
     get 
     { 
      int count; 

      rwLockList.EnterReadLock(); 

      count = internalList.Count; 

      rwLockList.ExitReadLock(); 

      return (count); 
     } 
    } 

    /// <summary> 
    /// Check whether Thread safe list is read only 
    /// </summary> 
    public bool IsReadOnly 
    { 
     get { return false; } 
    } 

    /// <summary> 
    /// Index of an item in the Thread safe list 
    /// </summary> 
    /// <param name="item"></param> 
    /// <returns></returns> 
    public int IndexOf(T item) 
    { 
     int itemIndex; 

     rwLockList.EnterReadLock(); 

     itemIndex = internalList.IndexOf(item); 

     rwLockList.ExitReadLock(); 

     return (itemIndex); 
    } 

    /// <summary> 
    /// Insert an item at a specified index in a Thread safe list 
    /// </summary> 
    /// <param name="index"></param> 
    /// <param name="item"></param> 
    public void Insert(int index, T item) 
    { 
     rwLockList.EnterWriteLock(); 

     if (index <= internalList.Count - 1 && index >= 0) 
     internalList.Insert(index,item); 

     rwLockList.ExitWriteLock(); 
    } 

    /// <summary> 
    /// Remove an item at a specified index in Thread safe list 
    /// </summary> 
    /// <param name="index"></param> 
    public void RemoveAt(int index) 
    { 
     rwLockList.EnterWriteLock(); 

     if (index <= internalList.Count - 1 && index >= 0) 
     internalList.RemoveAt(index); 

     rwLockList.ExitWriteLock(); 
    } 

    /// <summary> 
    /// Indexer for the Thread safe list 
    /// </summary> 
    /// <param name="index"></param> 
    /// <returns></returns> 
    public T this[int index] 
    { 
     get 
     { 
      T returnItem = default(T); 

      rwLockList.EnterReadLock(); 

      if (index <= internalList.Count - 1 && index >= 0) 
       returnItem = internalList[index];    

      rwLockList.ExitReadLock(); 

      return (returnItem); 
     } 
     set 
     { 
      rwLockList.EnterWriteLock(); 

      if (index <= internalList.Count - 1 && index >= 0) 
       internalList[index] = value; 

      rwLockList.ExitWriteLock(); 
     } 
    } 
} 
+4

为什么不使用['ConcurrentBag'](http://msdn.microsoft.com/en-us/library/dd381779(V = vs.110)的.aspx),或在并行命名空间的任何其他列表类型? – 2014-09-05 08:12:33

+0

只要访问列表,只要使用'lock'就好多了。而且,正如上面所写的枚举不是线程安全的。 – Zer0 2014-09-05 08:12:35

+0

为什么要创建并发列表而不是使用内置结构,如ConcurrentBag。 – i3arnon 2014-09-05 08:12:43

回答

4

实现自定义List<T>封装线程安全是很不值得的努力。每当您访问List<T>时,您最好只使用lock

但是,在性能密集型行业,我自己也遇到过这种情况,这成为了一个瓶颈。 lock的主要缺点是上下文切换的可能性,相对来说,在挂钟时间和CPU周期中都是非常昂贵的。

最好的解决方法是使用不变性。让所有读者访问一个不可变的列表,编写者使用Interlocked操作“更新”它以将其替换为新实例。这是一种无锁设计,可以实现读取的无同步和无锁写入(消除上下文切换)。

我会强调的是,在几乎所有情况下,这都是矫枉过正,我甚至不会考虑沿着这条路走下去,除非你肯定你需要并且你明白缺点。一些明显的问题是读者获取时间点快照并浪费内存来创建副本。

ImmutableList from Microsoft.Bcl.Immutable也值得一看。它完全是线程安全的。

+0

作为使用不可变列表的替代方法,您可以简单地在列表中要求时返回列表副本,如果列表不是太大,这是一个很好的解决方案。 (请注意,微软不可变列表,使自身的副本,只要您拨打一个明显变异操作,类似于字符串操作是如何工作的 - 这样也对内存的使用,如果名单是大重) – 2014-09-05 08:49:44

+0

有在以下链接的实现 - http://stackoverflow.com/questions/5874317/thread-safe-listt-property 这里的GetEnumerator以线程安全的方式与实施,以及如果我们修改(添加/删除)线程安全的,使用一个锁将服务于目的 – 2014-09-05 09:16:10

+0

的SynchronizedCollection 可以说是一个替代品,但它不是并发命名空间的一部分,因为它是早于.NET 4.0 – 2014-09-05 09:29:47

3

这不是线程安全的。

在枚举器返回后,方法GetEnumerator()不会保留任何锁,因此任何线程都可以自由地使用返回的枚举器而不用任何锁定来阻止它们这样做。

通常,尝试创建线程安全列表类型非常困难。

见一些讨论这个StackOverflow的主题:No ConcurrentList<T> in .Net 4.0?

1

如果您尝试使用某种读写器锁定,而不是读取和写入的简单锁定方案,那么您的并发读取可能会大大超过您的写入。在那种情况下,如Zer0所建议的copy-on-write方法可能是合适的。

在对相关问题的回答中,我发布了一个通用实用程序函数,可帮助将对任何数据结构的任何修改转换为线程安全且高度并行的操作。

代码

static class CopyOnWriteSwapper 
{ 
    public static void Swap<T>(ref T obj, Func<T, T> cloner, Action<T> op) 
     where T : class 
    { 
     while (true) 
     { 
      var objBefore = Volatile.Read(ref obj); 
      var newObj = cloner(objBefore); 
      op(newObj); 
      if (Interlocked.CompareExchange(ref obj, newObj, objBefore) == objBefore) 
       return; 
     } 
    } 
} 

使用

CopyOnWriteSwapper.Swap(ref _myList, 
    orig => new List<string>(orig), 
    clone => clone.Add("asdf")); 

更多,你可以用它做什么的详细信息,以及一对夫妇的注意事项可以在original answer找到。