我尝试使用Parallel.Foreach
. 我认为进行并行处理很简单,因为它没有同步问题。它基本上是一个蒙特卡洛树搜索,我在其中并行探索每个孩子。蒙特卡洛的东西并不是很重要,你只需要知道我有一个方法可以工作在某个树上,并且我Parallel.Foreach
在根子节点上调用它。这是进行并行调用的片段。
public void ExpandParallel(int time, Func<TGame, TGame> gameFactory)
{
int start = Environment.TickCount;
// Creating all of root's children
while (root.AvailablePlays.Count > 0)
Expand(root, gameInstance);
// Create the children games
var games = root.Children.Select(c =>
{
var g = gameFactory(gameInstance);
c.Play.Apply(g.Board);
return g;
}).ToArray();
// Create a task to expand each child
Parallel.ForEach(root.Children, (tree, state, i) =>
{
var game = games[i];
// Make sure we don't waste time
while (Environment.TickCount - start < time && !tree.Completed)
Expand(tree, game);
});
// Update (reset) the root data
root.Wins = root.Children.Sum(c => c.Wins);
root.Plays = root.Children.Sum(c => c.Plays);
root.TotalPayoff = root.Children.Sum(c => c.TotalPayoff);
}
委托是一个克隆工厂,因此Func<TGame, TGame>
每个孩子都有自己的游戏状态克隆。如果需要,我可以解释该Expand
方法的内部结构,但我可以保证它只访问当前子树和游戏实例的状态,并且没有static
任何这些类型的成员。我认为这可能Environment.TickCount
是导致争用的原因,但我进行了一个实验,只是EnvironmentTickCount
在一个Parallel.Foreach
循环内调用,并获得了近 100% 的处理器使用率。
我在 Core i5 上的使用率只有 45% 到 50%。