Tre*_*erg 5 c# multithreading threadpool
为了加快 C# 中物理对象的处理速度,我决定将线性更新算法更改为并行算法。我相信最好的方法是使用线程池,因为它是为完成作业队列而构建的。
当我第一次实现并行算法时,我为每个物理对象排队了一个作业。请记住,单个作业完成得相当快(更新力、速度、位置、检查与任何周围对象的旧状态的碰撞以使其线程安全等)。然后,我将使用单个等待句柄等待所有作业完成,每次物理对象完成时我都会递减一个互锁的整数(在达到零时,我然后设置等待句柄)。需要等待,因为我需要执行的下一个任务涉及更新所有对象。
我注意到的第一件事是性能太疯狂了。平均而言,线程池似乎运行得更快一些,但性能出现了巨大的峰值(每次更新大约 10 毫秒,随机跳跃到 40-60 毫秒)。我尝试使用 ANTS 对此进行分析,但是我无法深入了解峰值发生的原因。
我的下一个方法是仍然使用线程池,但是我将所有对象分成组。我最初只使用 8 个组,因为我的计算机的所有核心都是这样的。表演很棒。它的性能远远优于单线程方法,并且没有尖峰(每次更新大约 6 毫秒)。
我唯一想到的是,如果一项工作先于其他工作完成,就会有一个闲置的核心。因此,我将作业数量增加到20个左右,甚至增加到500个。正如我所料,它下降到了5ms。
所以我的问题如下:
对于你的两个问题,我的看法如下:
我想从问题 2(线程池如何工作)开始,因为它实际上是回答问题 1 的关键。线程池是作为(线程安全的)工作队列和组来实现的(无需详细介绍)工作线程(可以根据需要缩小或放大)。当用户调用时,QueueUserWorkItem任务被放入工作队列中。工作人员不断轮询队列并在空闲时接受工作。一旦他们设法接受任务,他们就会执行该任务,然后返回队列进行更多工作(这非常重要!)。因此,工作是由工人们按需完成的:当工人们变得闲置时,他们就会做更多的工作。
说了上面的内容,很容易看出问题 1 的答案是什么(为什么你会看到更细粒度的任务的性能差异):这是因为使用细粒度你可以获得更多的负载平衡(一个非常理想的属性) ,即您的工作人员或多或少地执行相同的工作量,并且所有核心都被统一利用。正如您所说,在粗粒度任务分配中,可能会有较长和较短的任务,因此一个或多个核心可能会落后,从而减慢整体计算速度,而其他核心则什么也不做。对于小任务,问题就消失了。每个工作线程一次执行一项小任务,然后返回执行更多任务。如果一个线程获取较短的任务,它将更频繁地进入队列,如果它需要较长的任务,它将更少地进入队列,因此事情是平衡的。
最后,当作业粒度太细时,并且考虑到池可能会扩大到超过 1K 线程,当所有线程返回执行更多工作时(这种情况经常发生),队列上会出现非常高的争用,这可能会导致对于您所看到的尖峰。如果底层实现使用阻塞锁来访问队列,那么上下文切换会非常频繁,这会严重影响性能,并且看起来相当随机。