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

时间:2016-05-10 10:26:46

标签: multithreading c#-4.0 .net-4.0 parallel-for

我正在测试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);
}

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

  

计数方法5:299250

而不是300000

这对并行性是错误的吗?

3 个答案:

答案 0 :(得分:1)

我认为来自@ 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的其他精彩示例。另一个选择是使用while循环和Interlocked.CompareExchange方法,但在您的情况下,我认为这不是非常重要的使用。

答案 1 :(得分:0)

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

您需要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. 锁定:
  2. 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. 失去增量:
    2. 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.Exchange( ref count_method_5, Interlocked.Read(ref count_method_5) + 1);

      而不是非常复杂的Interlocked.Increment(ref count_method_5);

答案 2 :(得分:0)

谢谢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++;
});

非常感谢!

克里斯托弗