ConcurrentBag <t>和lock(List <t>)哪个更快添加或删除?</t> </t>

时间:2015-03-27 17:48:53

标签: c# .net

我需要在List上使用一些线程安全操作。

通常我只是简单地使用:

lock(List<T>){
   List<T>.Add();
   List<T>.Remove();
}

我也知道还有另一种方法,使用ConcurrentBag<T>。但我不知道哪个更快或任何其他不同。

UPDATE1:

有些人建议我使用ConcurrentBag,因为这样更安全。但我担心它会让我的操作更慢。

我只需要很多线程需要在List中添加或删除一些对象,我只是想知道更好的性能方法。这就是我问的原因。

3 个答案:

答案 0 :(得分:6)

只需尝试一下,您就可以轻松衡量不同方法的效果!这就是我刚刚得到的:

lock list: 2,162s
ConcurrentBag: 7,264s
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

public class Test
{
    public const int NumOfTasks = 4;
    public const int Cycles = 1000 * 1000 * 4;

    public static void Main()
    {
        var list = new List<int>();
        var bag = new ConcurrentBag<int>();

        Profile("lock list", () => { lock (list) list.Add(1); });
        Profile("ConcurrentBag", () => bag.Add(1));
    }

    public static void Profile(string label, Action work)
    {
        var s = new Stopwatch();
        s.Start();

        List<Task> tasks = new List<Task>();

        for (int i = 0; i < NumOfTasks; ++i)
        {
            tasks.Add(Task.Factory.StartNew(() =>
            {
                for (int j = 0; j < Cycles; ++j)
                {
                    work();
                }
            }));
        }

        Task.WaitAll(tasks.ToArray());

        Console.WriteLine(string.Format("{0}: {1:F3}s", label, s.Elapsed.TotalSeconds));
    }
}

答案 1 :(得分:3)

请勿使用ConcurrentBag<T>替换锁定的List<T>,除非您确定了线程的访问模式,因为它使用了线程本地存储。< / p>

MSDN讨论了首选用法:

&#34; ConcurrentBag是一个线程安全的包实现,针对同一个线程生成和使用存储在包中的数据的情况进行了优化。&#34;

同样重要的是要注意List<T> 已订购ConcurrentBag<T> 无序。如果您不关心收藏中的订单,我会使用ConcurrentQueue<T>

关于性能,以下是ConcurrentBag<T>的一些代码。但要考虑的主要事情是,如果你执行Take并且你的线程本地存储空了,它将从其他昂贵的线程中窃取。

当它需要窃取它锁定时。另请注意,它可以在一个Take上锁定多次,因为TrySteal可能会失败并从Steal调用多次(未显示)。

private bool TrySteal(ConcurrentBag<T>.ThreadLocalList list, out T result, bool take)
{
  lock (list)
  {
    if (this.CanSteal(list))
    {
      list.Steal(out result, take);
      return true;
    }
    result = default (T);
    return false;
  }
}

CanSteal期间也可能有等待旋转。

private bool CanSteal(ConcurrentBag<T>.ThreadLocalList list)
{
  if (list.Count <= 2 && list.m_currentOp != 0)
  {
    SpinWait spinWait = new SpinWait();
    while (list.m_currentOp != 0)
      spinWait.SpinOnce();
  }
  return list.Count > 0;
} 

最后,即使添加也会导致锁定。

private void AddInternal(ConcurrentBag<T>.ThreadLocalList list, T item)
{
  bool lockTaken = false;
  try
  {
    Interlocked.Exchange(ref list.m_currentOp, 1);
    if (list.Count < 2 || this.m_needSync)
    {
      list.m_currentOp = 0;
      Monitor.Enter((object) list, ref lockTaken);
    }
    list.Add(item, lockTaken);
  }
  finally
  {
    list.m_currentOp = 0;
    if (lockTaken)
      Monitor.Exit((object) list);
  }
}

答案 2 :(得分:2)

List操作addremove是O(n),这意味着锁定的持续时间取决于列表的大小。列表越大,您的并发性就越少。但是,如果您总是添加到最后并从末尾删除,则实际上有一个堆栈。在这种情况下,addremove操作为O(1),您将拥有更短的锁。

ConcurrentBag被实现为链接列表的链接列表(每个线程一个。操作addtake是O(1),并且在一般情况下不需要锁定。通常可以避免锁定的事实意味着它可能更快。