使我的代码线程安全时遇到问题

Posted

技术标签:

【中文标题】使我的代码线程安全时遇到问题【英文标题】:Having problem making my code thread-safe 【发布时间】:2020-12-10 20:54:47 【问题描述】:

CreateNewRound() 方法在运行时被多个线程访问。假设 _game.CurrentRound = 99 并且两个线程同时访问该方法,它们都将 currentRoundId 初始化为 100,并且两个线程都添加了两个具有相同 roundId 的实体。但这是错误的,我不希望这种情况发生,因为回合应该是独特的和不同的。我该如何解决这个问题,以便线程一个添加一个第 100 轮的实体,另一个添加第 101 轮的实体。


public void CreateNewRound()

   var game = _cache.GetGameById(_session.gameId);
   var currentRoundId = game.CurrentRound + 1;

   var response = SomeAPI.SomeCall();
   if (response.responseCode == (int)responseCodes.Success)
    
      _dbContext.GameState.Add(new GameState()  RoundId = CurrentRoundId );
      _dbContext.SaveChanges();
   

【问题讨论】:

docs.microsoft.com/en-gb/dotnet/csharp/language-reference/… @Artavazd 是的,但您只有一个资源,在这种情况下,您必须使用监视器(或二进制信号量);无论如何,当其他线程已经在使用相同的资源时,您必须阻止其他线程执行某些操作,因此您不在乎这是否较慢,正确运行很重要。附言C# 中的监视器是作为锁实现的。 如果多个线程可以在同一轮中调用这个,你显然不能使用轮号作为唯一ID。您必须引入另一个(或额外的)ID 来消除歧义。 @Artavazd “不应用锁会使我的代码变慢吗?” - 很有可能。但这有问题吗? - 我建议实施解决问题的最简单的解决方案(即最少的更改)并对其进行基准测试。如果您不喜欢基准测试的结果:找到下一个更复杂的解决方案,将焦点从“最少的更改”转移到“速度”。然后再次测量... 是的。锁会使您的代码变慢。 完全与交通信号灯使汽车行驶速度变慢的方式相同 - 因此它们不会相互碰撞。基本上这是你的选择——要么接受你的代码运行错误的事实,要么接受你的代码等待其他代码完成的事实。你不能两者兼得。 (或者找到一个不依赖线程访问相同资源的解决方案,从而消除同步的需要) 【参考方案1】:

如果(且仅当)所有调用的方法都是纯的,即结果仅取决于输入参数,您可以简单地使用 interlocked.Increment 来确保 currentRound 对于每个调用都是唯一的:

    private int currentRound = 0;
    public void CreateNewRound()
    
        var thisRound = Interlocked.Increment(ref currentRound);
        var gamestate = CreateGameState(thisRound)
        // process game state
    

在大多数游戏中,下一轮将取决于上一轮的游戏状态。在这种情况下,您必须按顺序运行每一轮。典型的解决方案是为此使用锁:

    private int currentRound = 0;
    private object myLock = new object();
    private MyGameState gameState;
    public void CreateNewRound()
    
        lock (myLock)
        
            currentRound++;
            gameState = ComputeNextGameState(gameState, currentRound);
            // process game state
        
     

还有其他选择,例如分配一个特定线程来执行所有游戏状态更新,并让CreateNewRound 只要求更新线程进行更新。

【讨论】:

如果“进程游戏状态”是对可能需要一些时间的 API 的调用,会有什么问题吗?这将影响我的代码的性能。我说的对吗? @artavazd 这将取决于 API 调用的作用。如果 API 调用是纯的,则多个线程同时调用它并不重要,因此如果需要,可以在单独的线程上运行它是安全的。

以上是关于使我的代码线程安全时遇到问题的主要内容,如果未能解决你的问题,请参考以下文章

我应该始终使我的 java 代码线程安全,还是出于性能原因仅在需要时才这样做?

Java线程安全

[JCIP笔记] 如何设计一个线程安全的对象

python-进程与线程 三

CSRF 验证失败 - 当主机安全时,Referer 不安全

JAVA进阶21