在Parallel.Foreach中出现意外的线程争用

本文关键字:意外 线程 争用 Parallel Foreach | 更新日期: 2023-09-27 17:49:17

我尝试使用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引起了争论,但是我在Parallel.Foreach循环中运行了一个实验,只是调用EnvironmentTickCount,并且获得了接近100%的处理器使用率。

我在酷睿i5上的使用率只有45%到50%。

在Parallel.Foreach中出现意外的线程争用

这是GC抖动的常见症状。在不了解更多关于Expand方法内部的操作的情况下,我最好的猜测是这将是您的根本原因。也有可能一些共享数据访问也是罪魁祸首,要么是通过调用远程系统,要么是通过锁定对共享资源的访问。

在您做任何事情之前,您需要使用分析器或其他工具确定确切的原因。不要猜测,因为这只会浪费你的时间,也不要在这里等待答案,因为没有完整的程序就无法回答。正如您已经从实验中知道的那样,Parallel.ForEach中没有任何东西会导致这种情况。