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
操作add
和remove
是O(n),这意味着锁定的持续时间将取决于列表的大小。 列表越大,您的并发性就越少。 但是,如果您总是添加到最后并从末尾删除,则实际上有一个堆栈。 在这种情况下, add
和remove
操作是O(1),你将有更短的锁。
ConcurrentBag
实现为链接列表的链接列表(每个线程一个。操作add
和take
是O(1)并且在一般情况下不需要锁定。通常可以避免锁定的事实意味着它可能更快。