在 Parallel.ForEach 内部使用锁定的正确方法是什么?

问题描述 投票:0回答:4

我想学习在

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);
            }
        });
c# multithreading locking parallel.foreach thread-synchronization
4个回答
3
投票

如果您的应用程序是并发的(并行性是并发类型之一)并且您想使用线程安全的集合,则没有理由自行锁定集合。 Microsoft 提供了一堆并发集合,存在于 System.Collections.Concurrent 中 线程安全集合


2
投票

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
之外并行发生重要的处理.


1
投票

例如那种用法:

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;
    }

0
投票

最好的方法是第二种:

if (item.Active)
{
    lock (secondList)
        secondList.Add(item);
}

一般建议是:握住

lock
时做最少的工作,并尽早释放。可以在
lock
之外完成的工作,不要在
lock
内完成。

检查

item.Active
不需要同步,因此不需要将其放入
lock
内。您在
lock
中投入的工作越多,另一个线程在尝试获取相同的 Locker 对象时被阻塞的可能性就越大。这称为“争用”,会减慢您的应用程序速度。您可以通过查询并行执行前后的 Monitor.LockContentionCount
 属性来获取争用统计信息:

获取尝试获取监视器锁定时发生争用的次数。

增量 (
after - before

) 越小越好。

    

© www.soinside.com 2019 - 2024. All rights reserved.