C# 4 .NET 4.0 Parallel.For 长循环问题

C# 4 .NET 4.0 Parallel.For issue for long loops

我正在用 c# 4、.NET 4.0 测试一些并行化解决方案。

我有一些奇怪的结果,所以我想知道我做事的方式是否正确。

这是我的代码的描述:

//This will count the number of times we pass in the loop
private static double count_method_5 = 0;

//This will generate a MD5 hash
private static void GenerateMD5Hash(double i)
{
    var md5M = MD5.Create();
    byte[] data = Encoding.Unicode.GetBytes(Environment.UserName + i.ToString());
    byte[] result = md5M.ComputeHash(data);
} 

static void Main(string[] args)
{
    //Launch method Parallel for method 2
    var time9 = watch.ElapsedMilliseconds;
    int loop2 = 0;
    int limit2 = 300000;
    Parallel.For(loop2, limit2, new ParallelOptions { MaxDegreeOfParallelism = 8 }, i =>
    {
        GenerateMD5Hash(i);
        count_method_5++;
        loop2++;
    });
    var time10 = watch.ElapsedMilliseconds;
    Console.WriteLine("Parallel For second method  (method 5) Elapsed time :" + (time10 - time9) + "ms");    
    Console.WriteLine("Count method 5 : " + count_method_5);
}

这段代码给出了这个结果:

Count method 5 : 299250

而不是 300000

这是我们对并行性的误解吗?

您可能会遇到一些无法增加数字的情况,因为两个线程试图同时编辑变量。

您要么需要 lock 访问变量的代码(因此一次只有一个线程只能访问它),要么使用 Interlocked.Exchange( ref count_method_5, Interlocked.Read(ref count_method_5) + 1) 执行方法的线程安全更新。

实际上,考虑到这一点,也有可能一个线程正在读取该值,然后另一个线程在第一个线程之前递增它 - 所以你失去了那个增量。 lock 会解决这个问题,但 Interlocked.Exchange 本身不会。

也许最好的解决方案是同时使用两者?无论如何,这应该可以帮助您入门。

即:

Object lockObject = new Object();
Parallel.For(loop2, limit2, new ParallelOptions { MaxDegreeOfParallelism = 8 }, i =>
    {
        GenerateMD5Hash(i);
        lock(lockObject){
        Interlocked.Exchange( ref count_method_5, Interlocked.Read(ref count_method_5) + 1);}
        loop2++;
    });

我将尝试举例说明可能出现的问题:

  1. 锁定:

count_method_5: 1 Thread 1: updating count_method_5 Thread 2: tries to update count_method_5 and fails because Thread 1 is accessing it.

  1. 丢失增量:

count_method_5: 1 Thread 1: reads count_method_5 as 1 Thread 2: reads count_method_5 as 1 Thread 1: updates count_method_5 to 2 (1 + 1) Thread 2: updates count_method_5 to 2 (1 + 1)

因此,两个线程更新了它,但它只增加了 1。


更新:有人提醒我您可以使用 Interlocked.Increment(ref count_method_5);

而不是极其复杂的 Interlocked.Exchange( ref count_method_5, Interlocked.Read(ref count_method_5) + 1);

谢谢Simonalexander2005,你是对的!

我尝试了您的解决方案并且成功了!没想过变量访问并发!

可能 Interlocked.Read 调用中缺少 ref 关键字:

Parallel.For(loop2, limit2, new ParallelOptions { MaxDegreeOfParallelism = 8 }, i =>
{
    GenerateMD5Hash(i);
    lock (lockObject)
    {
    Interlocked.Exchange(ref count_method_5, Interlocked.Read(ref count_method_5) + 1);
    }
    loop2++;
});

非常感谢!

克里斯托夫

我认为来自@simonalexander2005 的解决方案有点复杂。为什么不使用 Interlocked.Increment 方法?在这种情况下,您可以移除循环的锁,这样性能会更好!

Parallel.For(loop2, limit2, new ParallelOptions { MaxDegreeOfParallelism = 8 }, i =>
{
    GenerateMD5Hash(i);
    Interlocked.Increment(ref count_method_5);
    Interlocked.Increment(ref loop2);
});

如果您需要添加一些其他值而不是 1,您可以使用 Interlocked.Add 方法,如下所示:

Parallel.For(loop2, limit2, new ParallelOptions { MaxDegreeOfParallelism = 8 }, i =>
{
    GenerateMD5Hash(i);
    Interlocked.Add(ref count_method_5, 5);
    Interlocked.Add(ref loop2, 5);
});

您可以找到 Interlocked here. Another option for you is to use a while loop with Interlocked.CompareExchange 方法的其他优秀示例,但在您的情况下,我认为这不是很重要。