for():在循环外部更新变量

时间:2022-03-08 00:23:47

I'm just looking in to the new .NET 4.0 features. With that, I'm attempting a simple calculation using Parallel.For and a normal for(x;x;x) loop.

我只是想看看。net 4.0的新特性。有了这个,我尝试了一个简单的并行计算。For和For (x;x;x)循环。

However, I'm getting different results about 50% of the time.

然而,我在50%的时间里得到了不同的结果。

long sum = 0;

Parallel.For(1, 10000, y =>
    {
        sum += y;
    }
);

Console.WriteLine(sum.ToString());

sum = 0;

for (int y = 1; y < 10000; y++)
{
   sum += y;
}
Console.WriteLine(sum.ToString());

My guess is that the threads are trying to update "sum" at the same time.
Is there an obvious way around it?

我的猜测是线程正在同时更新“sum”。有什么明显的办法吗?

7 个解决方案

#1


68  

You can't do this. sum is being shared across you parallel threads. You need to make sure that the sum variable is only being accessed by one thread at a time:

你不能这么做。sum是跨并行线程共享的。您需要确保sum变量一次只能被一个线程访问:

// DON'T DO THIS!
Parallel.For(0, data.Count, i =>
{
    Interlocked.Add(ref sum, data[i]);
});

BUT... This is an anti-pattern because you've effectively serialised the loop because each thread will lock on the Interlocked.Add.

但是…这是一个反模式,因为您已经有效地序列化了循环,因为每个线程都将锁定interlockey . add。

What you need to do is add sub totals and merge them at the end like this:

你需要做的是添加子总数,然后像这样合并它们:

Parallel.For<int>(0, result.Count, () => 0, (i, loop, subtotal) =>
    {
        subtotal += result[i];
        return subtotal;
    },
    (x) => Interlocked.Add(ref sum, x)
);

You can find further discussion of this on MSDN: http://msdn.microsoft.com/en-us/library/dd460703.aspx

您可以在MSDN上找到对此的进一步讨论:http://msdn.microsoft.com/en-us/library/dd460703.aspx

PLUG: You can find more on this in Chapter 2 on A Guide to Parallel Programming

PLUG:你可以在关于并行编程指南的第2章中找到更多

The following is also definitely worth a read...

以下内容绝对值得一读……

Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the .NET Framework 4 - Stephen Toub

并行编程的模式:理解和应用。net框架的并行模式4 - Stephen Toub

#2


16  

sum += y; is actually sum = sum + y;. You are getting incorrect results because of the following race condition:

+ = y总和;是sum = sum + y;由于下列比赛条件,你会得到不正确的结果:

  1. Thread1 reads sum
  2. Thread1读取和
  3. Thread2 reads sum
  4. Thread2读取和
  5. Thread1 calculates sum+y1, and stores the result in sum
  6. Thread1计算sum+y1,并将结果存储为sum
  7. Thread2 calculates sum+y2, and stores the result in sum
  8. Thread2计算sum+y2,并将结果存储为sum

sum is now equal to sum+y2, instead of sum+y1+y2.

sum现在等于sum+y2,而不是sum+y1+y2。

#3


5  

Your surmise is correct.

你的猜测是正确的。

When you write sum += y, the runtime does the following:

当您写入sum += y时,运行时执行以下操作:

  1. Read the field onto the stack
  2. 将字段读入堆栈。
  3. Add y to the stack
  4. 把y加到堆栈中
  5. Write the result back to the field
  6. 将结果写回字段

If two threads read the field at the same time, the change made by the first thread will be overwritten by the second thread.

如果两个线程同时读取字段,第一个线程所做的更改将被第二个线程覆盖。

You need to use Interlocked.Add, which performs the addition as a single atomic operation.

你需要使用联锁。添加,它作为单个原子操作执行添加。

#4


4  

Incrementing a long isn't an atomic operation.

递增长并不是一个原子操作。

#5


4  

I think it's important to distinguish that this loop is not capable of being partitioned for parallelism, because as has been mentioned above each iteration of the loop is dependent on the prior. The parallel for is designed for explicitly parallel tasks, such as pixel scaling etc. because each iteration of the loop cannot have data dependencies outside its iteration.

我认为很重要的一点是,要区分这个循环不能被划分为并行性,因为正如前面提到的,循环的每个迭代都依赖于前面。并行for是为显式并行任务设计的,例如像素缩放等,因为循环的每个迭代都不能在其迭代之外具有数据依赖关系。

Parallel.For(0, input.length, x =>
{
    output[x] = input[x] * scalingFactor;
});

The above an example of code that allows for easy partitioning for parallelism. However a word of warning, parallelism comes with a cost, even the loop I used as an example above is far far too simple to bother with a parallel for because the set up time takes longer than the time saved via parallelism.

上面的代码示例允许为并行性进行简单的分区。然而,需要提醒的是,并行性是有代价的,即使我在上面的例子中使用的循环也太简单了,不需要使用并行,因为设置时间比通过并行性节省的时间要长。

#6


3  

An important point no-one seems to have mentioned: For data-parallel operations (such as the OP's), it is often better (in terms of both efficiency and simplicity) to use PLINQ instead of the Parallel class. The OP's code is actually trivial to parallelize:

似乎没有人提到一个重要的问题:对于数据并行操作(比如OP),使用PLINQ比使用并行类更好(从效率和简单性两方面来说)。OP的代码对于并行化来说是微不足道的:

long sum = Enumerable.Range(1, 10000).AsParallel().Sum();

The above snippet uses the ParallelEnumerable.Sum method, although one could also use Aggregate for more general scenarios. Refer to the Parallel Loops chapter for an explanation of these approaches.

上面的代码片段使用了ParallelEnumerable。Sum方法,虽然也可以将聚合用于更一般的场景。有关这些方法的解释,请参阅并行循环章节。

#7


-1  

if there are two parameters in this code. For example

如果代码中有两个参数。例如

long sum1 = 0;
long sum2 = 0;

Parallel.For(1, 10000, y =>
    {
        sum1 += y;
        sum2=sum1*y;
    }
);

what will we do ? i am guessing that have to use array !

我们该怎么办?我猜一定要用数组!

#1


68  

You can't do this. sum is being shared across you parallel threads. You need to make sure that the sum variable is only being accessed by one thread at a time:

你不能这么做。sum是跨并行线程共享的。您需要确保sum变量一次只能被一个线程访问:

// DON'T DO THIS!
Parallel.For(0, data.Count, i =>
{
    Interlocked.Add(ref sum, data[i]);
});

BUT... This is an anti-pattern because you've effectively serialised the loop because each thread will lock on the Interlocked.Add.

但是…这是一个反模式,因为您已经有效地序列化了循环,因为每个线程都将锁定interlockey . add。

What you need to do is add sub totals and merge them at the end like this:

你需要做的是添加子总数,然后像这样合并它们:

Parallel.For<int>(0, result.Count, () => 0, (i, loop, subtotal) =>
    {
        subtotal += result[i];
        return subtotal;
    },
    (x) => Interlocked.Add(ref sum, x)
);

You can find further discussion of this on MSDN: http://msdn.microsoft.com/en-us/library/dd460703.aspx

您可以在MSDN上找到对此的进一步讨论:http://msdn.microsoft.com/en-us/library/dd460703.aspx

PLUG: You can find more on this in Chapter 2 on A Guide to Parallel Programming

PLUG:你可以在关于并行编程指南的第2章中找到更多

The following is also definitely worth a read...

以下内容绝对值得一读……

Patterns for Parallel Programming: Understanding and Applying Parallel Patterns with the .NET Framework 4 - Stephen Toub

并行编程的模式:理解和应用。net框架的并行模式4 - Stephen Toub

#2


16  

sum += y; is actually sum = sum + y;. You are getting incorrect results because of the following race condition:

+ = y总和;是sum = sum + y;由于下列比赛条件,你会得到不正确的结果:

  1. Thread1 reads sum
  2. Thread1读取和
  3. Thread2 reads sum
  4. Thread2读取和
  5. Thread1 calculates sum+y1, and stores the result in sum
  6. Thread1计算sum+y1,并将结果存储为sum
  7. Thread2 calculates sum+y2, and stores the result in sum
  8. Thread2计算sum+y2,并将结果存储为sum

sum is now equal to sum+y2, instead of sum+y1+y2.

sum现在等于sum+y2,而不是sum+y1+y2。

#3


5  

Your surmise is correct.

你的猜测是正确的。

When you write sum += y, the runtime does the following:

当您写入sum += y时,运行时执行以下操作:

  1. Read the field onto the stack
  2. 将字段读入堆栈。
  3. Add y to the stack
  4. 把y加到堆栈中
  5. Write the result back to the field
  6. 将结果写回字段

If two threads read the field at the same time, the change made by the first thread will be overwritten by the second thread.

如果两个线程同时读取字段,第一个线程所做的更改将被第二个线程覆盖。

You need to use Interlocked.Add, which performs the addition as a single atomic operation.

你需要使用联锁。添加,它作为单个原子操作执行添加。

#4


4  

Incrementing a long isn't an atomic operation.

递增长并不是一个原子操作。

#5


4  

I think it's important to distinguish that this loop is not capable of being partitioned for parallelism, because as has been mentioned above each iteration of the loop is dependent on the prior. The parallel for is designed for explicitly parallel tasks, such as pixel scaling etc. because each iteration of the loop cannot have data dependencies outside its iteration.

我认为很重要的一点是,要区分这个循环不能被划分为并行性,因为正如前面提到的,循环的每个迭代都依赖于前面。并行for是为显式并行任务设计的,例如像素缩放等,因为循环的每个迭代都不能在其迭代之外具有数据依赖关系。

Parallel.For(0, input.length, x =>
{
    output[x] = input[x] * scalingFactor;
});

The above an example of code that allows for easy partitioning for parallelism. However a word of warning, parallelism comes with a cost, even the loop I used as an example above is far far too simple to bother with a parallel for because the set up time takes longer than the time saved via parallelism.

上面的代码示例允许为并行性进行简单的分区。然而,需要提醒的是,并行性是有代价的,即使我在上面的例子中使用的循环也太简单了,不需要使用并行,因为设置时间比通过并行性节省的时间要长。

#6


3  

An important point no-one seems to have mentioned: For data-parallel operations (such as the OP's), it is often better (in terms of both efficiency and simplicity) to use PLINQ instead of the Parallel class. The OP's code is actually trivial to parallelize:

似乎没有人提到一个重要的问题:对于数据并行操作(比如OP),使用PLINQ比使用并行类更好(从效率和简单性两方面来说)。OP的代码对于并行化来说是微不足道的:

long sum = Enumerable.Range(1, 10000).AsParallel().Sum();

The above snippet uses the ParallelEnumerable.Sum method, although one could also use Aggregate for more general scenarios. Refer to the Parallel Loops chapter for an explanation of these approaches.

上面的代码片段使用了ParallelEnumerable。Sum方法,虽然也可以将聚合用于更一般的场景。有关这些方法的解释,请参阅并行循环章节。

#7


-1  

if there are two parameters in this code. For example

如果代码中有两个参数。例如

long sum1 = 0;
long sum2 = 0;

Parallel.For(1, 10000, y =>
    {
        sum1 += y;
        sum2=sum1*y;
    }
);

what will we do ? i am guessing that have to use array !

我们该怎么办?我猜一定要用数组!