0

在学校,我们上周开始使用多线程,现在我们已经在使用多处理了,我对它有点迷茫,所以我要向你解释这个问题。对于一个练习,我们必须制作一个模拟 10000 场游戏的赌场游戏模拟器,以便我们知道赌场赢得游戏的频率。所以我编写了模拟器,我有 5 种方法来运行游戏:

static void game(Croupier croupier)
{
    croupier.createNewCardDeck();
    croupier.shuffleCards();
    croupier.giveCardsToPlayers();
    croupier.countPlayerPoints();
    croupier.displayResults();
}

如果我在 10000 次迭代的经典 for 循环中调用游戏,它运行正常,大约需要 2 秒,银行赢了 50% 的时间。

如果我使用 Parallel.For,它会在 shuffleCards 上崩溃,因为(我认为)多个进程正在尝试同时编辑同一组卡片。

我的第一个想法是在我的 shuffleCards 上放置一个 Mutex,但它会减慢模拟速度,而使用并行编程的目的是提高速度。所以我想在不同的过程中分离数据(所以我不是 10000 次迭代,而是在 4 个过程中进行 2500 次,每个循环都有自己的荷官、玩家、卡片等......)

您认为解决此问题的最佳方法是什么?您是否有任何简单的教程来解释如何处理使用相同数据的并行工作?您会选择哪种解决方案?谢谢

编辑:ShuffleCard 方法

        List<Card> randomList = new List<Card>();

        Random r = new Random();
        int randomIndex = 0;

        while (_cards.Count > 0)
        {
            randomIndex = r.Next(0, _cards.Count); //Choose a random object in the list
            randomList.Add(_cards[randomIndex]); //add it to the new, random list
            _cards.RemoveAt(randomIndex); //remove to avoid duplicates
        }
        return randomList;

所以是的 _cards 是荷官的私有财产(调用 this._cards = shuffleCards() ,每个进程都有相同的卡片列表

4

4 回答 4

6

你的想法是要走的路:给每个“处理单元”(即线程、任务)自己的游戏桌(荷官、玩家、卡片)。就像在真正的赌场中一样,您可以拥有任意数量的游戏桌,同时玩所有游戏,彼此独立,因为它们不共享任何数据。每当游戏结束时,结果都会转移到银行(您只有一个)。因此,唯一必须同步的(与关键部分)是将结果汇总到银行中。

这个例子是并行编程的完美小例子,因为现实世界可以相当直观地建模成相应的类和算法。

于 2012-11-16T09:13:16.173 回答
0

要么给每个线程它自己的集合集,要么实现一个并发集合来实现它自己的线程锁定。

于 2012-11-16T09:22:08.023 回答
0

前面的答案确实是对的,目标是拆分工作以便尽快运行它。简而言之:将您的代码设计为并发的,以便它可以并行运行。

在每次运行中,我们有:

  • 荷官
  • 正在玩的游戏
  • 每位荷官一次只能玩一场游戏。
  • 比赛结果

我们需要做出的关于并发性的两个主要决定是

  • 我们如何在游戏之间共享或分发荷官?
  • 结果如何共享?

以下是荷官的不同选项:

  • 如果我们只有一个 Croupier,我们最终将没有并发。一次只能玩一种游戏。
  • 我们每场比赛可以有一名荷官。这样我们理论上可以同时运行每个游戏。
  • 每个处理单元可以有一个庄家。这将允许尽可能多的游戏运行,但这可能不会像每场游戏一个庄家一样平衡,因为在原始处理之外可能还有其他因素。想象一下,如果写入结果是一个冗长的 IO 操作,但不是 CPU 密集型操作。我们可以运行更多游戏,但荷官正在等待结果完成。

对于结果,我们可以:

  • 在我们收到结果时将结果输出到某个流。如果这是一个控制台,输出很容易变得乱码。
  • 将结果输出给依次处理结果的消费者。这是更好的选择,因为它允许在不影响游戏的情况下返回结果状态。

因此,总体决策应该始终围绕:如何使我的代码尽可能地友好,以允许托管系统尽可能地运行事物。

对于下面的示例,我为每个处理单元选择了一个 Croupier,不是因为它更好,而是因为其他答案中没有说明。

以下是一些示例代码,说明了其中一些想法:

void Main()
{
    const int NUMBER_OF_GAMES = 10000;

    // this is how we have a Croupier per thread.
    var threadLocalCroupier = new ThreadLocal<Croupier>(() => new Croupier());

    var results = from gameNumber in Enumerable.Range(0, NUMBER_OF_GAMES).AsParallel()
                let croupier = threadLocalCroupier.Value
                select game(croupier, gameNumber);

    foreach (var result in results) {
        Console.WriteLine("Game done {0}", result.GameNumber);
        // display or analyse results.
    }
}

static ResultOfGame game(Croupier croupier, int gameNumber)
{
    croupier.createNewCardDeck(gameNumber);
    croupier.shuffleCards();
    croupier.giveCardsToPlayers();
    croupier.countPlayerPoints();
    var results = croupier.getResults();

    return results;
}

class ResultOfGame {

    public int GameNumber { get; private set; }

    public ResultOfGame(int gameNumber) 
    {
        this.GameNumber = gameNumber;
    }

}

// Define other methods and classes here
class Croupier {

private int currentGame;

    public void createNewCardDeck(int gameNumber) {this.currentGame = gameNumber;}
    public void shuffleCards() {}
    public void giveCardsToPlayers() {}
    public void countPlayerPoints() {}
    public ResultOfGame getResults() {
        return new ResultOfGame(this.currentGame);
    }
}
于 2012-11-16T23:28:24.317 回答
0

所以我不是 10000 次迭代,而是在 4 个进程上执行 2500 次

可以同时使用任务和数据并行,例如,

        int noOfProcess = 4;
        Task[] t = new Task[noOfProcess];
        for (int i = 0; i < noOfProcess; i++)
        {
           t[i]= Task.Factory.StartNew(() =>
            {
                Parallel.For(0, 2500, (v) => game(..));
            });
        }
        Task.WaitAll(t); //If Synchronous is needed.

避免写入共享内存位置。在使用并行编程时检查这个 msdn 以了解一些陷阱。

于 2012-11-16T12:08:53.277 回答