任务并行库(TPL)是否处理竞争条件?

4

我正在尝试理解使用任务并行库相较于传统多线程的优势,但当我思考以下情况时,我卡住了:它是否处理了竞态条件,还是我们需要在代码中处理?

这里是我的代码:

 int depdt = 0;    
 Parallel.For(1, 10, mem =>
        {

            for (int dep = 1; dep <= 10; dep++)
            {
                depdt = dep;
                Console.WriteLine("MEMBER: " + mem + " " + "Dependent: " + dep);
            }

            Console.WriteLine("Dep Value: " + depdt + " " + "mem: " + mem);
        });
        Console.ReadKey();

我运行了几次,没有看到任何线程干扰/覆盖"depdt"变量。但我需要确认一下。(或者)为了使其线程安全,我是否应手动创建一个类实例,并像以下代码一样实现它以避免竞争条件。
 int depdt = 0;
        Parallel.For(1, 10, mem =>
        {
              Worker worker = new Worker();
              worker.DoWork(mem);

        });
        Console.ReadKey();

  public class Worker
{
    public void DoWork(int mem)
    {

        int depdt = 0;
        for (int dep = 1; dep <= 10; dep++)
        {
            depdt = dep;
            Console.WriteLine("MEMBER: " + mem + " " + "Dependent: " + dep);
        }

        Console.WriteLine("Dep Value: " + depdt  +" "+ "mem: "+ mem);
    }
}

回复 @yms:

我的意思是使用正常的线程时,变量depdt变得不可靠。下面是我的代码:

for (int mem = 1; mem <= 10; mem++)
        {
            var t= new Thread(state =>
            {
                for (int dep = 1; dep <= 10; dep++)
                {
                    depdt = dep;
                    Console.WriteLine("MEMBER: " + mem + " " + "Dependent: " + dep);
                }

                Console.WriteLine("Dep Value: " + depdt + " " + "mem: " + mem);
            });

            t.Start(string.Format("Thread{0}", mem));
        }
        Console.ReadKey();

这是我的输出屏幕:事实上,mem和dep变量都变得不可靠了。

enter image description here


请注意,4字节int变量的赋值通常是原子性的(https://dev59.com/eGsy5IYBdhLWcg3w0RbT)。另外,我认为没有必要引入一个新的类,你可以在lambda表达式中使用一个局部变量。 - yms
@yms 我的意思是,通常使用普通线程时,您会陷入“depdt”变量不可靠的状态。请参见我的以下代码及其与线程的结果。 - marak
@yms,我在问题中添加了更多信息以回答您的评论。谢谢。 - marak
2个回答

3
如果你期望你的程序总是输出Dep Value: 10,那么是的,你的程序可能会出现竞态条件,导致其他值被打印出来。为了演示这个问题,只需在内部循环中引入延迟即可:
int depdt = 0;
Parallel.For(1, 10, mem =>
{
    for (int dep = 1; dep <= 10; dep++)
    {
        depdt = dep;
        Console.WriteLine("MEMBER: " + mem + " " + "Dependent: " + dep);
        Thread.Sleep(mem * 100);   // delay introduced here
    }
    Console.WriteLine("Dep Value: " + depdt + " " + "mem: " + mem);
});
Console.ReadKey();

您的程序看起来行为正确的原因是内部循环执行所需时间非常短,可能在分配给线程的单个时间量子内完成。

为了避免竞态条件,您只需要将 depdt 声明移到传递给 Parallel.For 的匿名函数内部。 这将导致每个线程都拥有自己的变量,避免冲突。

Parallel.For(1, 10, mem =>
{
    int depdt = 0;
    for (int dep = 1; dep <= 10; dep++)
    {
        depdt = dep;
        Console.WriteLine("MEMBER: " + mem + " " + "Dependent: " + dep);
    }
    Console.WriteLine("Dep Value: " + depdt + " " + "mem: " + mem);
});

谢谢Douglas。所以,要点是TPL没有任何内置机制来处理竞态条件,它应该在我们的代码中处理。 - marak
1
@marak: 共享内存并行性本质上容易受到竞争条件的影响。由于您将变量声明为跨所有线程共享,因此预计多个线程可以同时访问它。这实际上有时是期望的行为,例如在终止后台操作的哨兵标志的情况下。 - Douglas

1

不,任务并行库默认情况下不处理竞争条件。您需要注意同步对共享资源的访问。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接