我想学习在
Parallel.ForEach
中使用锁定的最佳方法。我应该在迭代中锁定整个代码块,还是应该在执行任何过程之前只锁定要用作多线程安全的对象?
例如:
Parallel.ForEach(list, item =>
{
lock (secondList)
{
//consider other processes works in here
if (item.Active)
secondList.Add(item);
}
});
或
Parallel.ForEach(list, item =>
{
//consider other processes works in here
if (item.Active)
{
lock (secondList)
secondList.Add(item);
}
});
如果您的应用程序是并发的(并行性是并发类型之一)并且您想使用线程安全的集合,则没有理由自行锁定集合。 Microsoft 提供了一堆并发集合,存在于 System.Collections.Concurrent 中 线程安全集合
Parallel.ForEach
是一种尝试在代码中获得更多并行性的方法。 lock
倾向于减少代码中的并行性1。因此,想要将它们结合起来很少是正确的2。
正如 Olegl 建议,并发集合可能是避免
lock
的一种方法。
另一个有趣的方法是在这里使用 PLINQ 而不是
Parallel.ForEach
。已经2019年了,再写一个循环有什么有趣的?
这会做这样的事情:
secondList.AddRange(list.AsParallel.Where(item =>
{
//consider other processes works in here
return item.Active;
});
这允许您保留非线程安全的
secondList
集合,但仍然不用担心锁 - 因为这是您自己现有的线程调用 AddRange
最终会消耗 PLINQ 提供的 IEnumerable<T>
;因此只有一个线程正在将项目添加到集合中。
PLINQ 尝试调整缓冲选项,但可能无法实现足够好的工作,具体取决于输入的大小
list
以及它选择使用的线程数。如果您对它的任何加速不满意(或者它没有实现任何加速),请在注销之前尝试使用它提供的 WithXxx
方法。
如果我不得不在你的两个例子之间进行选择(假设它们在其他方面都是正确的),我会选择选项2,因为它确实less工作,同时持有一个正在受到所有其他工人。
1除非您知道将请求的所有锁都足够细粒度,以至于没有两个并行线程会尝试获取相同的锁。但如果我们知道这一点,为什么我们还要再次使用锁呢?
2所以我会大胆地说,当所有并行锁都在同一个锁对象上时,将它们组合起来“总是”不正确,除非在
lock
之外并行发生重要的处理.
例如那种用法:
public static T CastTo<T>(this ArrayOfKeyValueOfstringstringKeyValueOfstringstring[] item)
{
var obj = Activator.CreateInstance(typeof(T), true);
var padlock = new object();
Parallel.ForEach(typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public), prop =>
{
lock (padlock)
{
if (!prop.TryGetAttribute<GldnhrnFieldAttribute>(out var fieldAttribute))
return;
var code = fieldAttribute?.Code;
if (string.IsNullOrEmpty(code)) return;
SetPropertyValue(item, obj, prop);
}
});
return (T)obj;
}
如您所见,我想将我的数据投射到这里的班级。相同的问题具有不同的代码块,我应该锁定所有代码块还是应该仅在调用 SetPropertyValue 方法之前锁定?
public static T CastTo<T>(this ArrayOfKeyValueOfstringstringKeyValueOfstringstring[] item)
{
var obj = Activator.CreateInstance(typeof(T), true);
var padlock = new object();
Parallel.ForEach(typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public), prop =>
{
if (!prop.TryGetAttribute<GldnhrnFieldAttribute>(out var fieldAttribute))
return;
var code = fieldAttribute?.Code;
if (string.IsNullOrEmpty(code)) return;
lock (padlock)
SetPropertyValue(item, obj, prop);
});
return (T)obj;
}
最好的方法是第二种:
if (item.Active)
{
lock (secondList)
secondList.Add(item);
}
一般建议是:握住
lock
时做最少的工作,并尽早释放。可以在 lock
之外完成的工作,不要在 lock
内完成。
检查
item.Active
不需要同步,因此不需要将其放入lock
内。您在 lock
中投入的工作越多,另一个线程在尝试获取相同的 Locker 对象时被阻塞的可能性就越大。这称为“争用”,会减慢您的应用程序速度。您可以通过查询并行执行前后的 Monitor.LockContentionCount
属性来获取争用统计信息:获取尝试获取监视器锁定时发生争用的次数。
增量 (
after - before
) 越小越好。