Parallel.ForEach 搜索未找到正确的值
Parallel.ForEach search doesn't find the correct value
这是我第一次尝试并行编程。
在我的真实应用程序中使用它之前,我正在编写一个测试控制台应用程序,但我似乎无法正确使用它。当我 运行 this 时,并行搜索总是比顺序搜索快,但并行搜索永远找不到正确的值。我做错了什么?
我在没有使用分区程序的情况下尝试过(只是Parallel.For
);它比顺序循环慢并且给出了错误的数字。我看到一个 Microsoft 文档说对于简单的计算,使用 Partitioner.Create
可以加快速度。所以我试过了,但仍然得到了错误的值。然后我看到了Interlocked
,但是我觉得我用错了
如有任何帮助,我们将不胜感激
Random r = new Random();
Stopwatch timer = new Stopwatch();
do {
// Make and populate a list
List<short> test = new List<short>();
for (int x = 0; x <= 10000000; x++)
{
test.Add((short)(r.Next(short.MaxValue) * r.NextDouble()));
}
// Initialize result variables
short rMin = short.MaxValue;
short rMax = 0;
// Do min/max normal search
timer.Start();
foreach (var amp in test)
{
rMin = Math.Min(rMin, amp);
rMax = Math.Max(rMax, amp);
}
timer.Stop();
// Display results
Console.WriteLine($"rMin: {rMin} rMax: {rMax} Time: {timer.ElapsedMilliseconds}");
// Initialize parallel result variables
short pMin = short.MaxValue;
short pMax = 0;
// Create list partioner
var rangePortioner = Partitioner.Create(0, test.Count);
// Do min/max parallel search
timer.Restart();
Parallel.ForEach(rangePortioner, (range, loop) =>
{
short min = short.MaxValue;
short max = 0;
for (int i = range.Item1; i < range.Item2; i++)
{
min = Math.Min(min, test[i]);
max = Math.Max(max, test[i]);
}
_ = Interlocked.Exchange(ref Unsafe.As<short, int>(ref pMin), Math.Min(pMin, min));
_ = Interlocked.Exchange(ref Unsafe.As<short, int>(ref pMax), Math.Max(pMax, max));
});
timer.Stop();
// Display results
Console.WriteLine($"pMin: {pMin} pMax: {pMax} Time: {timer.ElapsedMilliseconds}");
Console.WriteLine("Press enter to run again; any other key to quit");
} while (Console.ReadKey().Key == ConsoleKey.Enter);
示例输出:
rMin: 0 rMax: 32746 Time: 106
pMin: 0 pMax: 32679 Time: 66
Press enter to run again; any other key to quit
Interlocked.Exchange
仅对 Exchange 是线程安全的,每个 Math.Min
和 Math.Max
都可以带有竞争条件。您应该分别为每个批次计算 min/max,然后合并结果。
使用像 Interlocked
class 这样的低锁定技术是棘手且高级的。考虑到您在多线程方面的经验并不过分,我会说简单而可靠 lock
:
object locker = new object();
//...
lock (locker)
{
pMin = Math.Min(pMin, min);
pMax = Math.Max(pMax, max);
}
像这样进行并行搜索的正确方法是为每个使用的线程计算本地值,然后在最后合并这些值。这确保仅在最后阶段需要同步:
var items = Enumerable.Range(0, 10000).ToList();
int globalMin = int.MaxValue;
int globalMax = int.MinValue;
Parallel.ForEach<int, (int Min, int Max)>(
items,
() => (int.MaxValue, int.MinValue), // Create new min/max values for each thread used
(item, state, localMinMax) =>
{
var localMin = Math.Min(item, localMinMax.Min);
var localMax = Math.Max(item, localMinMax.Max);
return (localMin, localMax); // return the new min/max values for this thread
},
localMinMax => // called one last time for each thread used
{
lock(items) // Since this may run concurrently, synchronization is needed
{
globalMin = Math.Min(globalMin, localMinMax.Min);
globalMax = Math.Max(globalMax, localMinMax.Max);
}
});
如您所见,这比常规循环要复杂得多,而且它甚至没有做任何像分区这样的花哨操作。优化的解决方案可以在更大的块上工作以减少开销,但为简单起见省略了这一点,看起来 OP 已经意识到这些问题。
请注意,多线程编程很困难。虽然在操场上而不是在真实的程序中尝试这些技术是个好主意,但我仍然建议您应该从研究线程安全的潜在危险开始,很容易找到这方面的好资源。
并不是所有的问题都会像这样明显错误,并且很容易导致百万分之一的问题,或者仅在 cpu 负载高时,或仅在单个 CPU 系统,或仅在代码投入生产后很久才检测到的问题。每当多个线程可能同时读写同一内存时,保持偏执是一个好习惯。
我还建议学习不可变数据类型和纯函数,因为一旦涉及多个线程,它们就更安全、更容易推理。
这是我第一次尝试并行编程。
在我的真实应用程序中使用它之前,我正在编写一个测试控制台应用程序,但我似乎无法正确使用它。当我 运行 this 时,并行搜索总是比顺序搜索快,但并行搜索永远找不到正确的值。我做错了什么?
我在没有使用分区程序的情况下尝试过(只是Parallel.For
);它比顺序循环慢并且给出了错误的数字。我看到一个 Microsoft 文档说对于简单的计算,使用 Partitioner.Create
可以加快速度。所以我试过了,但仍然得到了错误的值。然后我看到了Interlocked
,但是我觉得我用错了
如有任何帮助,我们将不胜感激
Random r = new Random();
Stopwatch timer = new Stopwatch();
do {
// Make and populate a list
List<short> test = new List<short>();
for (int x = 0; x <= 10000000; x++)
{
test.Add((short)(r.Next(short.MaxValue) * r.NextDouble()));
}
// Initialize result variables
short rMin = short.MaxValue;
short rMax = 0;
// Do min/max normal search
timer.Start();
foreach (var amp in test)
{
rMin = Math.Min(rMin, amp);
rMax = Math.Max(rMax, amp);
}
timer.Stop();
// Display results
Console.WriteLine($"rMin: {rMin} rMax: {rMax} Time: {timer.ElapsedMilliseconds}");
// Initialize parallel result variables
short pMin = short.MaxValue;
short pMax = 0;
// Create list partioner
var rangePortioner = Partitioner.Create(0, test.Count);
// Do min/max parallel search
timer.Restart();
Parallel.ForEach(rangePortioner, (range, loop) =>
{
short min = short.MaxValue;
short max = 0;
for (int i = range.Item1; i < range.Item2; i++)
{
min = Math.Min(min, test[i]);
max = Math.Max(max, test[i]);
}
_ = Interlocked.Exchange(ref Unsafe.As<short, int>(ref pMin), Math.Min(pMin, min));
_ = Interlocked.Exchange(ref Unsafe.As<short, int>(ref pMax), Math.Max(pMax, max));
});
timer.Stop();
// Display results
Console.WriteLine($"pMin: {pMin} pMax: {pMax} Time: {timer.ElapsedMilliseconds}");
Console.WriteLine("Press enter to run again; any other key to quit");
} while (Console.ReadKey().Key == ConsoleKey.Enter);
示例输出:
rMin: 0 rMax: 32746 Time: 106
pMin: 0 pMax: 32679 Time: 66
Press enter to run again; any other key to quit
Interlocked.Exchange
仅对 Exchange 是线程安全的,每个 Math.Min
和 Math.Max
都可以带有竞争条件。您应该分别为每个批次计算 min/max,然后合并结果。
使用像 Interlocked
class 这样的低锁定技术是棘手且高级的。考虑到您在多线程方面的经验并不过分,我会说简单而可靠 lock
:
object locker = new object();
//...
lock (locker)
{
pMin = Math.Min(pMin, min);
pMax = Math.Max(pMax, max);
}
像这样进行并行搜索的正确方法是为每个使用的线程计算本地值,然后在最后合并这些值。这确保仅在最后阶段需要同步:
var items = Enumerable.Range(0, 10000).ToList();
int globalMin = int.MaxValue;
int globalMax = int.MinValue;
Parallel.ForEach<int, (int Min, int Max)>(
items,
() => (int.MaxValue, int.MinValue), // Create new min/max values for each thread used
(item, state, localMinMax) =>
{
var localMin = Math.Min(item, localMinMax.Min);
var localMax = Math.Max(item, localMinMax.Max);
return (localMin, localMax); // return the new min/max values for this thread
},
localMinMax => // called one last time for each thread used
{
lock(items) // Since this may run concurrently, synchronization is needed
{
globalMin = Math.Min(globalMin, localMinMax.Min);
globalMax = Math.Max(globalMax, localMinMax.Max);
}
});
如您所见,这比常规循环要复杂得多,而且它甚至没有做任何像分区这样的花哨操作。优化的解决方案可以在更大的块上工作以减少开销,但为简单起见省略了这一点,看起来 OP 已经意识到这些问题。
请注意,多线程编程很困难。虽然在操场上而不是在真实的程序中尝试这些技术是个好主意,但我仍然建议您应该从研究线程安全的潜在危险开始,很容易找到这方面的好资源。
并不是所有的问题都会像这样明显错误,并且很容易导致百万分之一的问题,或者仅在 cpu 负载高时,或仅在单个 CPU 系统,或仅在代码投入生产后很久才检测到的问题。每当多个线程可能同时读写同一内存时,保持偏执是一个好习惯。
我还建议学习不可变数据类型和纯函数,因为一旦涉及多个线程,它们就更安全、更容易推理。