具有多个线程的并行因子

时间:2015-03-19 09:36:45

标签: c# .net multithreading parallel-processing factorial

我试图制作一个并行计算数字因子的函数,仅用于测试目的。 让我们说我的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}暂停主线程吗?

2 个答案:

答案 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;
    }

希望这有帮助。