使用Reader Writer Lock创建线程安全列表

时间:2014-09-05 08:10:03

标签: c# .net multithreading c#-4.0 task-parallel-library

完全编辑早期版本,以下实现可以是线程安全列表实现。我只需要知道它是否真的是线程安全的,我知道性能明智仍然存在问题。目前版本正在使用ReaderWriterLockSlim,我有另一个使用Lock的实现,做同样的工作

使用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();
        }
    }
}

3 个答案:

答案 0 :(得分:4)

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

但是,在我自己处于业绩密集型行业的情况下,有些情况会成为瓶颈。 lock的主要缺点是上下文切换的可能性,相对而言,在挂钟时间和CPU周期中都非常昂贵。

解决这个问题的最佳方法是使用不变性。让所有读者访问不可变列表和作者&#34;更新&#34;它使用Interlocked操作将其替换为新实例。这是一种无锁设计,使读取无需同步并且无锁定写入(消除了上下文切换)。

我要强调的是,在几乎所有情况下这都是矫枉过正的,我甚至不会考虑沿着这条路走下去,除非你需要积极的并且了解其缺点。其中一些显而易见的是读者获取时间点快照并浪费内存创建副本。

来自ImmutableList

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

答案 1 :(得分:3)

这不是线程安全的。

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

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

请参阅此StackOverflow线程进行讨论:No ConcurrentList<T> in .Net 4.0?

答案 2 :(得分:1)

如果您尝试使用某种读取器/写入器锁定,而不是简单的读取和写入锁定方案,则并发读取可能会大大超过您的写入次数。在这种情况下,Zer0建议的写时复制方法可能是合适的。

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

<强>代码

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中的一些注意事项。