【问题标题】:Parallel factorial with multiple threads多线程并行阶乘
【发布时间】:2015-03-19 09:36:45
【问题描述】:

我正在尝试创建一个并行计算数字阶乘的函数,仅用于测试目的。 假设我的 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));
    }
}

有时有效,有时它会给我中间结果,有时会发生死锁。

为什么会这样? Thread.Sleep(10) 不应该暂停主线程直到我得到最终结果吗?

【问题讨论】:

  • 对于初学者来说,如果一个线程在其他线程完成之前将 done 设置为 true 怎么办?
  • 尝试适当的线程同步,例如一个信号量并等待所有线程结束。
  • @RagtimeWilly 这就是为什么我把那个部分放在一个锁块中,这样一个线程一次可以使部分结果相乘。只有最后一个应该设置为 true (通过检查正确是否等于正在计算的阶乘。
  • @darkdante - 但“最后一个”可以在其他一个之前完成。
  • @Damien_The_Unbeliever 谢谢,我错过了:)

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


【解决方案1】:

我建议查看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));
}

【讨论】:

  • @darkdante 没问题,很乐意提供帮助。
【解决方案2】:

您可以将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;
    }

希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多