线程求和计算错误



我的任务是,要在某个范围内总结数字,以实现我必须使用线程来分开计算。我将数字划分为零件,并为每个部分使用了一个线程。

 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);
    }
}

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

这里有多个错误

  • 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# - 循环和lambda表达式。

  • 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
                    }
                );
            }
        }
    );
    // ...
}

让我们将其全部togheter:

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

那是正确的。

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

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

最新更新