qak*_*mak 5 .net c# concurrency
我需要在List上使用一些线程安全操作.
通常我只是简单地使用:
lock(List<T>)
{
List<T>.Add();
List<T>.Remove();
}
Run Code Online (Sandbox Code Playgroud)
我也知道还有另一种方法,使用List<T>.但我不知道哪个更快或任何其他不同.
UPDATE1:
有些人建议我使用ConcurrentBag,因为这样更安全.但我担心它会让我的操作更慢.
我只是有很多线程需要在List中添加或删除一些对象,我只是想知道更好的性能方法.这就是我问的原因.
除非您确定线程的访问模式,否则不要ConcurrentBag<T>用于替换锁定List<T>,因为它在幕后使用线程本地存储。
MSDN 谈到首选用法:
"ConcurrentBag<T>是一个线程安全的 bag 实现,针对同一个线程将生产和消费存储在 bag 中的数据的场景进行了优化。"
这也是要注意,重要的List<T>是有序的,并ConcurrentBag<T>是无序的。如果您不关心收藏中的顺序,我会使用ConcurrentQueue<T>.
关于性能,下面是一些来自ConcurrentBag<T>. 但要考虑的主要事情是,如果您执行 aTake并且您的线程本地存储为空,它将从其他线程中窃取,这是昂贵的。
当它需要窃取时,注意它是锁定的。另请注意,它可以多次锁定一个,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;
}
}
Run Code Online (Sandbox Code Playgroud)
在 期间也可能有旋转等待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;
}
Run Code Online (Sandbox Code Playgroud)
最后,即使添加也会导致锁定。
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);
}
}
Run Code Online (Sandbox Code Playgroud)
只需尝试一下,您就可以轻松测量不同方法的性能!这就是我刚才得到的:
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));
}
}
Run Code Online (Sandbox Code Playgroud)