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++;
});
我将尝试举例说明可能出现的问题:
- 锁定:
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.
- 丢失增量:
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
方法的其他优秀示例,但在您的情况下,我认为这不是很重要。
我正在用 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++;
});
我将尝试举例说明可能出现的问题:
- 锁定:
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.
- 丢失增量:
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
方法的其他优秀示例,但在您的情况下,我认为这不是很重要。