在 Parallel.ForEach 方法内共享 List<T> 时是否需要同步?

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

我刚刚看到以下代码片段:

public List<string> ProcessText(List<student> students)
{
    List<student> result = new();

    Parallel.ForEach(students, (st) => 
    {
        if (string.IsNullOrEmpty(st.Name))
        {
            throw new Exception("Name cannot be empty");
        }

        result.Add(st);
    });

    return result;
}

这让我想到,因为

Parallel.ForEach
将在不同线程之间分配给定的方法参数,即学生,当它想要将对象添加到列表中时,它不需要任何同步,例如使用
lock
或并发集合而不是
List<T>

现在考虑我们像这样更改代码:

public List<string> ProcessText(List<student> students)
{
    List<student> result = new();

    Parallel.ForEach(students, (st) => 
    {
        if (result.Any(x=>x.Name == st.Name))
        {
            // do some thing and ignore the rest of the code and go for the next student
        }

        if (string.IsNullOrEmpty(st.Name))
        {
            throw new Exception("Name cannot be empty");
        }

        result.Add(st);
    });

    return result;
}

现在引用了

List<student>
并读取了其中的一些值,所以需要同步?为什么?

提前致谢

c# concurrency synchronization thread-safety task-parallel-library
1个回答
0
投票

这让我想到,因为 Parallel.Foreach 将在不同线程之间分配给定的方法参数,即学生,当它想要将对象添加到列表中时,它不需要任何同步

这是不正确的。列表不是线程安全的。所以添加项目时需要同步。一般来说,多个线程读取同一块内存是安全的。但是,一旦有了并发读取和写入,或者只是并发写入,就需要某种形式的同步。

List<T>
肯定是在写,所以需要同步。这段代码的结果可能是学生失踪,或者被添加两次,或者异常,或者其他什么,只是不要这样做。

您的第二个示例更加不安全,因为您同时进行并发读取和写入。

Environment.TickCount
应该是线程安全的,但可能也不是很有用。它将返回自系统启动以来的毫秒数,并且可能使用系统时钟的 16ms 分辨率,因此除非您有大量学生,或者
Environment.TickCount
调用非常慢,否则他们可能会获得相同的值所有学生。

最后我不建议在这样一个简单的例子中使用任何类型的并发代码。我还建议在将任何类型的多线程代码用于任何重要的事情之前阅读和学习更多有关线程安全的知识。多线程错误因难以发现和调试而臭名昭著。

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