【问题标题】:Method local variable being modified inside a Parallel.For. How thread safe is this?在 Parallel.For 中修改方法局部变量。这有多线程安全?
【发布时间】:2014-06-04 13:41:29
【问题描述】:

我有这个代码sn-p:

int totalData = result.Data.Count;
int count = 0;
Parallel.ForEach(result.Data, data =>
{
    try
    {
        EventRange importedEntity = ImportEntity(auxResult.EntityName, data);
        count++;
        EntityImported(importedEntity, count, totalData);
    }
    catch (Exception e)
    {
        exceptions.Enqueue(e);
    }
});

EntityImported 是一个事件,它应该说明已经处理了多少实体,以及我应该处理多少实体。我担心在 lambda 中递增计数的线程安全性,以及您建议采取哪些步骤来确保始终使用正确的计数变量值触发事件。

【问题讨论】:

    标签: c# multithreading thread-safety task-parallel-library parallel.foreach


    【解决方案1】:

    目前它根本不是线程安全的。

    您可以改用Interlocked.Increment(ref count),但通常最好为每个线程设置一个“本地”值,并在最后对它们求和。这样,除了分配要处理的项目之外,您不需要任何类型的跨线程数据流。

    Parallel.ForEach 的重载就是为此目的而设计的 - 请查看文档底部附近的示例,因为它正在执行与您的代码非常相似的操作非常。 (它维护一个本地计数,然后在最后对计数求和。)不幸的是,由于您需要在每次迭代中引发事件的方式,这对您的特定情况没有帮助 - 但 通常这是一个更好的方法。

    在这种情况下,您应该在事件引发代码中使用Interlocked.Increment 的结果:

    Parallel.ForEach(result.Data, data =>
    {
        try
        {
            EventRange importedEntity = ImportEntity(auxResult.EntityName, data);
            int newCount = Interlocked.Increment(ref count);
            EntityImported(importedEntity, newCount, totalData);
        }
        catch (Exception e)
        {
            exceptions.Enqueue(e);
        }
    });
    

    这样一来,每次计数都会引发一个事件(因此一个为 0,一个为 1,一个为 2,等等)。

    【讨论】:

    • 谢谢乔恩,虽然我不确定我是否理解这种超载如何帮助我。如果我理解正确,它允许我将某个值传递给 lambda,以正常方式对其求和,然后对其进行最终操作,以最大限度地减少必要的联锁操作的数量。如果我需要对已处理实体的总量进行操作,它将为我服务。但是,我的问题是,在处理每个实体时,我需要使用当前处理的实体数量来引发一个事件。我是不是误解了什么,或者超载对我没有好处?
    • @Uri:啊,我错过了。在这种情况下,只需回退到使用 Interlocked.Increment。
    • @Uri:在 Interlocked.Increment 和对 EntityImported 的调用之间,您仍然有一个问题,计数可能已经改变。优化器可能会针对该值优化返回内存,但可能不会。它可能没有那么快,但您可能希望在 lambda 中创建一个局部变量,在递增计数之前锁定,然后复制本地计数。例如 => 进口实体 = blah();内部计数; lock(lock_object){ interlocked.inc(count); internalcount=count;} EntityImported(count,importedEntity);
    • @Bengie:比赛是一个好点,但没有必要使用锁定。只需使用 Interlocked.Increment 的返回值 - 请参阅我编辑的示例代码答案。
    • @JonSkeet:我刚刚学到了一些新东西,Interlocked.Increment 返回值。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-07
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-28
    • 1970-01-01
    相关资源
    最近更新 更多