我试图制作一个并行计算数字因子的函数,仅用于测试目的。 让我们说我的cpu上有4个内核,所以我将分解"问题"分4个。
说,我做了这个:
public class FactorialPTest
{
public static object _locker = new object();
public static long Factorial(int x)
{
long result = 1;
int right = 0;
int nr = x;
bool done = false;
for (int i = 0; i < nr; i += (nr / 4))
{
int step = i;
new Thread(new ThreadStart(() =>
{
right = (step + nr / 4) > nr ? nr : (step + nr / 4);
long chunkResult = ChunkFactorial(step + 1, right);
lock (_locker)
{
result *= chunkResult;
if (right == nr)
done = true;
}
})).Start();
}
while(!done)
{
Thread.Sleep(10);
}
return result;
}
public static long ChunkFactorial(int left, int right)
{
//Console.WriteLine("left: {0} ; right: {1}", left, right);
Console.WriteLine("ChunkFactorial Thread ID :" + Thread.CurrentThread.ManagedThreadId);
if (left == right)
return left == 0 ? 1 : left;
else return right * ChunkFactorial(left, right - 1);
}
public static void main()
{
Console.WriteLine(Factorial(15));
}
}
有时候工作,有时会给我中间结果,有时会发生死锁。
为什么会这样?在我得到最终结果之前,不应该{0}暂停主线程吗?
答案 0 :(得分:2)
我建议调查Task Parallel Library。除其他外,它将抽象出许多与多线程相关的低问题。
您可以使用任务表示每个工作块,添加到集合中,然后等待它们全部完成:
public static long Factorial(int x)
{
long result = 1;
int right = 0;
int nr = x;
bool done = false;
var tasks = new List<Task>();
for (int i = 0; i < nr; i += (nr / 4))
{
int step = i;
tasks.Add(Task.Run(() =>
{
right = (step + nr / 4) > nr ? nr : (step + nr / 4);
long chunkResult = ChunkFactorial(step + 1, right);
lock (_locker)
{
result *= chunkResult;
}
}));
}
Task.WaitAll(tasks.ToArray());
return result;
}
在您的原始代码中,最后一个块可以设想完成它的工作,而right
将等于nr
,即使其他块没有被计算出来。此外,right
在所有线程之间共享,因此这也可能导致一些不可预测的结果,即所有线程都试图使用此变量同时保存不同的值。
通常,如果可能,您应该尽量避免在线程之间共享状态。上面的代码可以通过让每个任务返回它的结果来改进,然后使用这些结果来计算最终的代码:
public static long Factorial(int x)
{
int nr = x;
var tasks = new List<Task<long>>();
for (int i = 0; i < nr; i += (nr / 4))
{
int step = i;
tasks.Add(Task.Run(() =>
{
int right = (step + nr / 4) > nr ? nr : (step + nr / 4);
return ChunkFactorial(step + 1, right);
}));
}
Task.WaitAll(tasks.ToArray());
return tasks.Select(t => t.Result).Aggregate(((i, next) => i * next));
}
答案 1 :(得分:1)
您可以使用Parallel.For
重载中的一个进行聚合,它将处理并行性,工作负载分区以及结果聚合。对于long
类型的结果,如果我没有弄错的话,你只能做21阶乘。添加checked {...}
以捕获溢出也是有意义的。代码看起来像:
public long CalculateFactorial(long value)
{
var result = 1L;
var syncRoot = new object();
checked
{
Parallel.For(
// always 1
1L,
// target value
value,
// if need more control, add { MaxDegreeOfParallelism = 4}
new ParallelOptions(),
// thread local result init
() => 1L,
// next value
(i, state, localState) => localState * i,
// aggregate local thread results
localState =>
{
lock (syncRoot)
{
result *= localState;
}
}
);
}
return result;
}
希望这有帮助。