【问题标题】:c# multiple tasks do not run in parallelc#多个任务不并行运行
【发布时间】:2016-11-21 18:39:57
【问题描述】:

我尝试创建一个多线程骰子滚动模拟 - 只是为了好奇,多线程编程的乐趣以及向其他人展示“随机结果”的效果(很多人无法理解,如果你在其中掷六次拉普拉斯骰子一行,你已经有 1、2、3、4、5 下一个滚动不是 6。)。为了向他们展示 m 骰子的 n 个掷骰的分布情况,我创建了这段代码。

好吧,结果很好,但即使我为每个程序运行单线程的骰子创建了一个新任务。 多线程可以合理地模拟“数百万”次使用 6 个或更多骰子的重掷,因为完成时间会迅速增长。

我从 msdn 中阅读了几个示例,这些示例都表明应该同时运行多个任务。 有人可以给我一个提示,为什么这段代码不使用很多线程/内核? (甚至没有,当我尝试一次运行 400 个骰子并重掷 1000 万次时)

首先我初始化存储结果的锯齿状数组。第一个维度:每个骰子 1 个条目,第二个维度将是每个骰子滚动的眼睛的分布。

接下来我创建一个任务数组,每个任务返回一个结果数组(第二维,如上所述) 这些数组中的每一个都有 6 个条目,代表拉普拉斯 W6 骰子的每一面。如果掷骰结果为 1 只眼睛,则第一个条目 [0] 增加 +1。因此,您可以可视化每个值的滚动频率。

然后我使用一个普通的 for 循环来启动所有线程。在所有线程启动之前,没有迹象表明要等待线程。

最后,我等待所有人完成并总结结果。换了也没关系

Task.WaitAll(任务);到 Task.WhenAll(任务);

我的问题是:为什么该代码不使用我的 CPU 的多个内核?我需要改变什么?

提前致谢!

代码如下:

private void buttonStart_Click(object sender, RoutedEventArgs e)
    {      
        int tries = 1000000;
        int numberofdice = 20   ;
        int numberofsides = 6; // W6 = 6
        var rnd = new Random();

        int[][] StoreResult = new int[numberofdice][];
        for (int i = 0; i < numberofdice; i++)
        {
            StoreResult[i] = new int[numberofsides];
        }

        Task<int[]>[] tasks = new Task<int[]>[numberofdice];


        for (int ctr = 0; ctr < numberofdice; ctr++)
        {

            tasks[ctr] = Task.Run(() =>
            {
                int newValue = 0;
                int[] StoreTemp = new int[numberofsides]; // Array that represents how often each value appeared
                for (int i = 1; i <= tries; i++) // how often to roll the dice
                {
                    newValue = rnd.Next(1, numberofsides + 1); // Roll the dice; UpperLimit for random int EXCLUDED from range 
                    StoreTemp[newValue-1] = StoreTemp[newValue-1] + 1; //increases value corresponding to eyes on dice by +1
                }
                return StoreTemp;
            });
                StoreResult[ctr] = tasks[ctr].Result; // Summing up the individual results for each dice in an array

        }
        Task.WaitAll(tasks);

          // do something to visualize the results - not important for the question

        }
    }

【问题讨论】:

  • 当您调用Result 时,您会一直阻塞直到Task 完成。
  • 尽量将您的解释性文本保持在理解和重现问题所需的最低限度。您的问题可能在这一行:StoreResult[ctr] = tasks[ctr].Result; 因为对于Task.Result:“访问属性的 get 访问器会阻塞调用线程,直到异步操作完成;它相当于调用 Wait 方法”。解决方法是删除该行,然后在 Task.WaitAll() 之后迭代结果。
  • @Quantic:我不知道,我会试试你的建议... ------ BINGO!这解决了问题!

标签: c# multithreading parallel-processing task


【解决方案1】:

这里的问题是tasks[ctr].Result.Result 部分本身在将结果 int 数组存储到 StoreResult 之前等待函数完成。相反,请在 Task.WaitAll 之后创建一个新循环以获取结果。

【讨论】:

  • Task.WhenAll 在这里可能更好。
【解决方案2】:

您可以考虑执行 Parallel.Foreach 循环,而不是为此手动创建单独的任务。

正如其他人所指出的,当您尝试聚合时,您最终只是等待每个单独的任务完成,因此这实际上不是多线程的。

非常重要的说明: C# 随机数生成器 is not thread safe(有关该主题的讨论,另请参阅此 MSDN blog post)。不要在多个线程之间共享同一个实例。来自documentation

...随机对象不是线程安全的。如果您的应用调用 Random 来自多个线程的方法,您必须使用同步对象 确保只有一个线程可以访问随机数生成器 一次。如果您不确保 Random 对象在 线程安全的方式,调用返回随机数的方法返回 0。

另外,为了挑剔,使用Task 与执行多线程并不是一回事;实际上,虽然您在这里执行多线程,但也可以使用 async/await 执行纯异步、非多线程代码。这主要用于 I/O 绑定操作,在这些操作中创建一个单独的线程只是为了等待结果在很大程度上是没有意义的(但希望允许调用线程在等待结果时执行其他工作)。

我认为您在分配给主数组时不必担心线程安全(假设每个线程只分配给数组中的特定索引并且没有其他人分配给相同的内存位置) ;当多个线程同时访问/修改 shared 可变状态时,您只需要担心锁定。如果我没看错的话,这个 is 可变状态(但它不是 shared 可变状态)。

【讨论】:

  • 谢谢,我用 msdn 博客文章中建议的第三个版本替换了 Random 方法。在这种特殊情况下,UI 不需要响应,因为除了“等待结果”之外别无他法。尽管如此,我了解其中的好处,并且可能会在以后扩展该计划。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-28
  • 2017-01-26
  • 1970-01-01
  • 2023-01-27
  • 1970-01-01
  • 1970-01-01
  • 2011-04-15
相关资源
最近更新 更多