2014-09-05 73 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找到。