线程求和计算错误

时间:2017-12-03 17:56:26

标签: c# .net multithreading

我的任务是,总结一些范围内的数字,实现我必须使用线程来分离计算。 我将数字划分为零件,并为每个零件使用了一个螺纹。

 public class ParallelCalc
{
    public  long resultLong;
    private Thread[] threads;
    private List<long> list = new List<long>();

    public long MaxNumber { get; set; }
    public int ThreadsNumber { get; set; }

    public event CalcFinishedEventHandler finished;

    public ParallelCalc(long MaxNumber, int ThreadsNumber)
    {
        this.MaxNumber = MaxNumber;
        this.ThreadsNumber = ThreadsNumber;
        this.threads = new Thread[ThreadsNumber];
    }

    public void Start()
    {
        Stopwatch sw = new Stopwatch();

        for (int i = 0; i < ThreadsNumber; i++)
        {

            threads[i] = new Thread(() =>  Sum(((MaxNumber / ThreadsNumber) * i) + 1, 
                MaxNumber / ThreadsNumber * (i + 1)));

            if (i == ThreadsNumber - 1)
            {
                threads[i] = new Thread(() => Sum(((MaxNumber / ThreadsNumber) * i) + 1,
                                    MaxNumber));
            }

            sw.Start();
            threads[i].Start();
        }

        while (threads.All(t => t.IsAlive));
        sw.Stop();

        finished?.Invoke(this,
           new CalcFinishedEventArgs()
           {
               Result = list.Sum(),
               Time = sw.ElapsedMilliseconds
           });
    }


    private void Sum(long startNumber, long endnumber)
    {
        long result = 0;

        for (long i = startNumber; i <= endnumber; i++)
        {
            result += i;
        }

        list.Add(result);

    }

}

结果必须是数字的总和,但是,由于列表中的线程异步分配,它是不正确的。请指出错误。

1 个答案:

答案 0 :(得分:3)

这里有不止一件事,支撑你自己...

  • Start会创建Stopwatch sw,但您会在循环的每次迭代中调用sw.Start。只启动一次。

  • 如果i == ThreadsNumber - 1评估为true,则会让Thread变为垃圾。我没理解为什么......

    (MaxNumber / ThreadsNumber) * (i + 1) WHEN i == ThreadsNumber - 1
    =
    (MaxNumber / ThreadsNumber) * (ThreadsNumber - 1 + 1)
    =
    (MaxNumber / ThreadsNumber) * (ThreadsNumber)
    =
    MaxNumber
    

    你有舍入问题吗?像这样改写:

    ((i + 1) * MaxNumber) / ThreadsNumber
    

    通过划分最后一个,可以避免舍入问题。

  • 您正在等待线程while (threads.All(t => t.IsAlive));。您也可以使用Thread.Join或更好,让线程在完成后通知您。

  • lambdas中的范围在i上有一个闭包。您需要注意C# - For loop and the lambda expressions

  • List<T>不是线程安全的。我建议使用一个简单的数组(你知道线程的数量)并告诉每个线程只存储在与它们对应的位置。

  • 您还没有考虑在第一次调用Start之前发生第二次调用会发生什么情况。

因此,我们将有一个输出数组:

var output = new long[ThreadsNumber];

一个用于主题:

var threads = new Thread[ThreadsNumber];

嗯,几乎就像我们应该创建一个类。

我们将有秒表:

var sw = new Stopwatch();

让我们开始一次:

sw.Start();

现在创建一个for线程:

for (var i = 0; i < ThreadsNumber; i++)
{
    // ...
}

拥有i的副本以防止出现问题:

for (var i = 0; i < ThreadsNumber; i++)
{
    var index = i;
    // ...
}

计算当前线程的范围:

for (var i = 0; i < ThreadsNumber; i++)
{
    var index = i;
    var start = 1 + (i * MaxNumber) / ThreadsNumber;
    var end = ((i + 1) * MaxNumber) / ThreadsNumber;
    // ...
}

我们需要以这样的方式编写Sum,以便我们可以将输出存储在数组中:

private void Sum(long startNumber, long endNumber, int index)
{
    long result = 0;
    for (long i = startNumber; i <= endnumber; i++)
    {
        result += i;
    }
    output[index] = result;
}

嗯......等等,还有更好的方法......

private static void Sum(long startNumber, long endNumber, out long output)
{
    long result = 0;
    for (long i = startNumber; i <= endNumber; i++)
    {
        result += i;
    }
    output = result;
}

嗯......不,我们可以做得更好......

private static long Sum(long startNumber, long endNumber)
{
    long result = 0;
    for (long i = startNumber; i <= endNumber; i++)
    {
        result += i;
    }
    return result;
}

创建Thread

for (var i = 0; i < ThreadsNumber; i++)
{
    var index = i;
    var start = 1 + (i * MaxNumber) / ThreadsNumber;
    var end = ((i + 1) * MaxNumber) / ThreadsNumber;
    threads[i] = new Thread(() => output[index] = Sum(start, end));
    // ...
}

然后开始Thread

for (var i = 0; i < ThreadsNumber; i++)
{
    var index = i;
    var start = 1 + (i * MaxNumber) / ThreadsNumber;
    var end = ((i + 1) * MaxNumber) / ThreadsNumber;
    threads[i] = new Thread(() => {output[index] = Sum(start, end);});
    threads[i].Start();
}

我们真的要等待吗?

想想,想想......

我们跟踪有多少线程待处理......当它们全部完成时,我们调用该事件(并停止秒表)。

var pendingThreads = ThreadsNumber;

// ...

for (var i = 0; i < ThreadsNumber; i++)
{
    // ...
    threads[i] = new Thread
    (
        () =>
        {
            output[index] = Sum(start, end);
            if (Interlocked.Decrement(ref pendingThreads) == 0)
            {
                sw.Stop();
                finished?.Invoke
                (
                    this,
                    new CalcFinishedEventArgs()
                    {
                        Result = output.Sum(),
                        Time = sw.ElapsedMilliseconds
                    }
                );
            }
        }
    );
    // ...
}

让我们全力以赴:

void Main()
{
    var pc = new ParallelCalc(20, 5);
    pc.Finished += (sender, args) =>
    {
        Console.WriteLine(args);
    };
    pc.Start();
}

public class CalcFinishedEventArgs : EventArgs
{
    public long Result {get; set;}
    public long Time {get; set;}
}

public class ParallelCalc
{
    public long MaxNumber { get; set; }
    public int ThreadsNumber { get; set; }

    public event EventHandler<CalcFinishedEventArgs> Finished;

    public ParallelCalc(long MaxNumber, int ThreadsNumber)
    {
        this.MaxNumber = MaxNumber;
        this.ThreadsNumber = ThreadsNumber;
    }

    public void Start()
    {
        var output = new long[ThreadsNumber];
        var threads = new Thread[ThreadsNumber];
        var pendingThreads = ThreadsNumber;
        var sw = new Stopwatch();
        sw.Start();
        for (var i = 0; i < ThreadsNumber; i++)
        {
            var index = i;
            var start = 1 + (i * MaxNumber) / ThreadsNumber;
            var end = ((i + 1) * MaxNumber) / ThreadsNumber;
            threads[i] = new Thread
            (
                () =>
                {
                    output[index] = Sum(start, end);
                    if (Interlocked.Decrement(ref pendingThreads) == 0)
                    {
                        sw.Stop();
                        Finished?.Invoke
                        (
                            this,
                            new CalcFinishedEventArgs()
                            {
                                Result = output.Sum(),
                                Time = sw.ElapsedMilliseconds
                            }
                        );
                    }
                }
            );
            threads[i].Start();
        }
    }

    private static long Sum(long startNumber, long endNumber)
    {
        long result = 0;
        for (long i = startNumber; i <= endNumber; i++)
        {
            result += i;
        }
        return result;
    }
}

输出:

Result
210 

Time
0 

太快了......让我输入:

var pc = new ParallelCalc(2000000000, 5);
pc.Finished += (sender, args) =>
{
    Console.WriteLine(args);
};
pc.Start();

输出:

Result
2000000001000000000 

Time
773

And that is correct

是的,这段代码负责多次调用Start的情况。请注意,它每次都为输出创建一个新数组,并创建一个新的线程数组。这样,它就不会绊倒。

我让错误处理给你。提示:MaxNumber / ThreadsNumber - &gt;除以0,(i + 1) * MaxNumber - &gt;溢出,更不用说output.Sum() - &gt;溢出。