【问题标题】:Thread Summing computation error线程求和计算错误
【发布时间】:2018-05-17 05:06:49
【问题描述】:

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

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

    }

}

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

【问题讨论】:

  • 您正在使用许多共享变量,其中大多数的类型被认为对于多线程访问是不安全的。既然您自己付出了这么少的努力来纠正问题,为什么还要我们诊断您的问题?
  • 我唯一要问的是在这种情况下使用什么来从多个线程中获得正确的结果。
  • 您应该改用Parallel.For Loop
  • 不只是列表,您还捕获了i,然后大量使用它。
  • 计算你将拥有多少段并使用它来创建new List&lt;&gt; (capacity),或者只是创建一个数组。然后您可以将list.Add(result) 替换为list[index] = result,这 线程安全的。您的答案仍然不对,请参阅我关于捕获的评论。

标签: c# .net multithreading


【解决方案1】:

这里的问题不止一件事,振作起来……

  • 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 =&gt; t.IsAlive));。您也可以使用Thread.Join 或更好,让线程在完成时通知您。

  • lambda 中的范围在i 上有一个闭包。你需要小心C# - For loop and the lambda expressions

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

  • 您尚未考虑如果在第一次调用结束之前对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 -> 除以 0,(i + 1) * MaxNumber -> 溢出,更不用说output.Sum() -> 溢出。

【讨论】:

    猜你喜欢
    • 2018-05-11
    • 2022-08-19
    • 2021-07-25
    • 1970-01-01
    • 1970-01-01
    • 2011-05-05
    • 1970-01-01
    • 2011-11-18
    • 1970-01-01
    相关资源
    最近更新 更多