2016-11-21 118 views
0

我试图创建一个多线程的骰子滚动模拟 - 只是为了好奇心,多线程编程的喜悦以及向其他人展示“随机结果”的效果(许多人无法理解,如果你连续掷6次掷骰子,你已经有1,2,3,4,5,下一个掷骰子不是6)。为了向他们展示m个骰子的n个卷的分布,我创建了这个代码。c#多任务不能并行运行

那么,结果是好的,但即使我为每个骰子创建一个新的任务程序运行单线程。 多线程将是合理的,以模拟6百万或更多骰子的“数百万”重卷,因为结束时间将快速增长。

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

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

接下来我创建一个任务数组,每个返回结果数组(第二维,如上所述) 每个数组都有6个条目,分别代表laplace W6骰子的每个边。如果骰子掷出1眼,则第一个输入[0]增加+1。因此,您可以查看每个值的滚动频率。

然后我使用一个普通的for-loop来启动所有线程。没有任何迹象表明等到一个线程,直到所有开始。

最后我等待所有人完成并总结结果。它不会有任何区别,如果改变

Task.WaitAll(tasks);到 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 

     } 
    } 
+0

当你调用Result时,你会阻塞,直到完成Task任务。 – juharr

+1

尽量让您的解释性文字达到理解和重现问题所需的最低限度。你的问题可能在这一行:'StoreResult [ctr] = tasks [ctr] .Result;'因为['Task.Result'](https://msdn.microsoft.com/en-us/library/dd321468( v = vs.110).aspx):“访问属性的get访问器将阻止调用线程,直到异步操作完成;这相当于调用Wait方法”。解决方法是删除该行,然后在Task.WaitAll()后面迭代结果。 – Quantic

+0

@Quantic: 我不知道,并会给你一个尝试的建议... ------ 宾果! 解决了这个问题! – EinsteinsNierenstein

回答

2

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

+0

Task.WhenAll在这里可能更好。 – EJoshuaS

+0

WhenAll不会改变任何东西 - 已经尝试过。 – EinsteinsNierenstein

+0

@ alex-f 解决了这个问题。 谢谢。 – EinsteinsNierenstein

1

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

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

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

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

此外,刚要挑剔的方法,使用Task不真的和多线程一样;实际上,在这里您正在执行多线程,但也可以使用async/await来执行纯粹的异步,非多线程代码。这主要用于I/O绑定操作,在这种操作中,创建单独的线程只是为了等待结果(但希望允许调用线程在等待结果时执行其他工作)是毫无意义的。

我不认为你应该担心分配给主数组时的线程安全性(假设每个线程只分配给数组中的特定索引,并且没有其他人分配给相同的内存位置);当多线程同时访问/修改共享可变状态时,您只需担心锁定。如果我正确读取这个,这个可变状态(但它不是共享可变状态)。

+0

谢谢,我用msdn博客文章中建议的第三个版本替换了Random方法。 在这种特殊情况下,用户界面无需做出响应,因为除了“等待结果”以外,没有别的可做的事情了。尽管如此,我理解这些好处,并可能会在稍后扩展该程序。 – EinsteinsNierenstein