ConcurrentBag 和锁定(List )哪个更快或者删除?

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

通常我只是简单地使用:

lock(List){ List.Add(); List.Remove(); } 

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

UPDATE1:

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

我只是有很multithreading需要在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(); var bag = new ConcurrentBag(); 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 tasks = new List(); 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)); } } 

不要使用ConcurrentBag来替换锁定的List除非您确定线程的访问模式,因为它使用了封装下的线程本地存储。

MSDN讨论了首选用法:

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

同样重要的是要注意List有序的ConcurrentBag无序的 。 如果您不关心集合中的顺序,我会使用ConcurrentQueue

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

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

 private bool TrySteal(ConcurrentBag.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.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.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); } } 

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

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