并行嵌套操作 return 奇怪的结果
Parallel nested operations return weird results
我正在尝试为我的代码使用并行库,但我遇到了一个奇怪的问题。
我做了一个简短的程序来演示这种行为。简而言之,我做了 2 个循环(一个在另一个循环中)。第一个循环生成一个包含 200 个整数的随机数组,第二个循环将所有数组添加到一个大列表中。
问题是,最后,我没有得到 200 整数的倍数,而是我看到一些运行没有等待随机数组完全加载。
很难解释,所以这里是示例代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace TestParallel
{
class Program
{
static int RecommendedDegreesOfParallelism = 8;
static int DefaultMaxPageSize = 200;
static void Main(string[] args)
{
int maxPage = 50;
List<int> lstData = new List<int>();
Parallel.For(0, RecommendedDegreesOfParallelism, new ParallelOptions() { MaxDegreeOfParallelism = RecommendedDegreesOfParallelism },
(index) =>
{
int cptItems = 0;
int cptPage = 1 - RecommendedDegreesOfParallelism + index;
int idx = index;
do
{
cptPage += RecommendedDegreesOfParallelism;
if (cptPage > maxPage) break;
int Min = 0;
int Max = 20;
Random randNum = new Random();
int[] test2 = Enumerable
.Repeat(0, DefaultMaxPageSize)
.Select(i => randNum.Next(Min, Max))
.ToArray();
var lstItems = new List<int>();
lstItems.AddRange(test2);
var lstRes = new List<int>();
lstItems.AsParallel().WithDegreeOfParallelism(8).ForAll((item) =>
{
lstRes.Add(item);
});
Console.WriteLine($"{Task.CurrentId} = {lstRes.Count}");
lstData.AddRange(lstRes);
cptItems = lstRes.Count;
} while (cptItems == DefaultMaxPageSize);
}
);
Console.WriteLine($"END: {lstData.Count}");
Console.ReadKey();
}
}
}
这是执行日志:
4 = 200
1 = 200
2 = 200
3 = 200
6 = 200
5 = 200
7 = 200
8 = 200
1 = 200
6 = 194
2 = 191
5 = 200
7 = 200
8 = 200
4 = 200
5 = 200
3 = 182
4 = 176
8 = 150
7 = 200
5 = 147
1 = 200
7 = 189
1 = 200
1 = 198
END: 4827
我们可以看到一些循环 return 少于 200 项。
怎么可能?
这不是线程安全的:
lstItems.AsParallel().WithDegreeOfParallelism(8).ForAll((item) =>
{
lstRes.Add(item);
});
来自 List<T>
的文档:
It is safe to perform multiple read operations on a List, but
issues can occur if the collection is modified while it's being read.
To ensure thread safety, lock the collection during a read or write
operation. To enable a collection to be accessed by multiple threads
for reading and writing, you must implement your own synchronization.
没有明确提及,但.Add()
在多个线程同时调用时也会失败。
解决方案是在上面的循环中锁定对 List<T>.Add()
的调用,但如果这样做,它可能会比在单个线程中的循环中添加项目慢。
var locker = new object();
lstItems.AsParallel().WithDegreeOfParallelism(8).ForAll((item) =>
{
lock (locker)
{
lstRes.Add(item);
}
});
我正在尝试为我的代码使用并行库,但我遇到了一个奇怪的问题。 我做了一个简短的程序来演示这种行为。简而言之,我做了 2 个循环(一个在另一个循环中)。第一个循环生成一个包含 200 个整数的随机数组,第二个循环将所有数组添加到一个大列表中。 问题是,最后,我没有得到 200 整数的倍数,而是我看到一些运行没有等待随机数组完全加载。 很难解释,所以这里是示例代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace TestParallel
{
class Program
{
static int RecommendedDegreesOfParallelism = 8;
static int DefaultMaxPageSize = 200;
static void Main(string[] args)
{
int maxPage = 50;
List<int> lstData = new List<int>();
Parallel.For(0, RecommendedDegreesOfParallelism, new ParallelOptions() { MaxDegreeOfParallelism = RecommendedDegreesOfParallelism },
(index) =>
{
int cptItems = 0;
int cptPage = 1 - RecommendedDegreesOfParallelism + index;
int idx = index;
do
{
cptPage += RecommendedDegreesOfParallelism;
if (cptPage > maxPage) break;
int Min = 0;
int Max = 20;
Random randNum = new Random();
int[] test2 = Enumerable
.Repeat(0, DefaultMaxPageSize)
.Select(i => randNum.Next(Min, Max))
.ToArray();
var lstItems = new List<int>();
lstItems.AddRange(test2);
var lstRes = new List<int>();
lstItems.AsParallel().WithDegreeOfParallelism(8).ForAll((item) =>
{
lstRes.Add(item);
});
Console.WriteLine($"{Task.CurrentId} = {lstRes.Count}");
lstData.AddRange(lstRes);
cptItems = lstRes.Count;
} while (cptItems == DefaultMaxPageSize);
}
);
Console.WriteLine($"END: {lstData.Count}");
Console.ReadKey();
}
}
}
这是执行日志:
4 = 200 1 = 200 2 = 200 3 = 200 6 = 200 5 = 200 7 = 200 8 = 200 1 = 200 6 = 194 2 = 191 5 = 200 7 = 200 8 = 200 4 = 200 5 = 200 3 = 182 4 = 176 8 = 150 7 = 200 5 = 147 1 = 200 7 = 189 1 = 200 1 = 198 END: 4827
我们可以看到一些循环 return 少于 200 项。 怎么可能?
这不是线程安全的:
lstItems.AsParallel().WithDegreeOfParallelism(8).ForAll((item) =>
{
lstRes.Add(item);
});
来自 List<T>
的文档:
It is safe to perform multiple read operations on a List, but issues can occur if the collection is modified while it's being read. To ensure thread safety, lock the collection during a read or write operation. To enable a collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.
没有明确提及,但.Add()
在多个线程同时调用时也会失败。
解决方案是在上面的循环中锁定对 List<T>.Add()
的调用,但如果这样做,它可能会比在单个线程中的循环中添加项目慢。
var locker = new object();
lstItems.AsParallel().WithDegreeOfParallelism(8).ForAll((item) =>
{
lock (locker)
{
lstRes.Add(item);
}
});