上周接手一个基于 Python 的德州扑克 Web 项目,本来想顺手做下 Review 和性能优化。结果第一跑就被泼了冷水:后端一进牌局就卡死,19 项后端测试挂了 12 项,前端按钮点了没反应,结算金额还经常对不上------连一局完整的「发牌→翻牌→转牌→河牌→摊牌」都跑不通。后面三天踩了并发、业务逻辑、前后端联动一堆坑,总算全解决,这里把过程记下来。
卡死的根因在线程锁
最先解决的是服务卡死。查日志发现核心加锁逻辑有问题:原代码用普通 threading.Lock,但加锁方法存在递归调用------比如处理玩家下注时先加锁校验筹码,校验不通过又会递归调校验方法,再次请求同一个锁。普通 Lock 是非递归锁,同一线程重复加锁直接死锁,这也是一进牌局就卡的核心原因。我一开始还以为是业务逻辑死循环,翻了三遍代码才定位到锁,换成 threading.RLock(可重入锁)后卡死立刻消失。
这个坑的教训很实在:凡有递归调用、嵌套加锁的场景,优先用 RLock,别为了省那点性能用普通 Lock,debug 的时间远比锁的开销贵。
德州扑克的细节坑比想象多
卡死修好,测试还是过不了,核心是几个专属业务逻辑有硬伤。
joinGame 接口没做防重复点击。前端点「加入游戏」时网络一慢,用户会连点好几次,后端没做幂等校验,就给同一玩家建多个游戏实例,后续牌局逻辑全乱。我在接口里加了请求唯一标识校验,同一请求 1 秒内重复提交直接返回,从根上杜绝重复加入。
边池(Side Pot)计算也错了。原代码只支持单池,遇到多人 All-in、筹码不同的场景就乱套:玩家 A 有 1000 筹码 All-in,B 有 2000 跟注,C 有 3000 跟注,原代码把三人筹码混在一起算,导致 A 最多只能拿 1000 对应的奖池,多出来的部分分错。后来重写边池逻辑,按 All-in 顺序拆分奖池,现在支持最多 9 人桌的多边池,结算金额和线下规则完全对齐。
牌型评估的优先级也反了。原 _indexed_hand_result 先判同花再判同花顺,导致同花顺被误判成同花。我把同花顺的优先级提到同花前面,又补了 20+ 组边界用例(皇家同花顺、公对牌型、边池场景下的牌型对比等),现在牌型判断准确率 100%。还有 heads-up(单挑)的行动顺序:原代码默认小盲注先行动,但单挑时是大盲注后行动,改完逻辑才符合规则。
前端渲染不统一比逻辑 bug 还烦
后端逻辑全通,前端还有问题:状态更新不及时、加注额显示和实际下注不一致、牌局结束界面卡死。查下来都是渲染的坑:结算弹窗缺必要的 DOM 节点,摊牌时渲染不出玩家手牌;渲染逻辑不统一,有的状态用 v-if、有的用 v-show,同一状态在不同模块展示不一致;加注标签(raiseLabel)没和后端下注逻辑同步,加了注前端还显示旧额度,导致玩家下错注。后来所有状态更新都收敛到 renderGame 这一个入口,加了 raiseLabel 和后端下注接口的同步校验,前端交互才全解决。
收尾
这次一共改了 120+ 行后端、80+ 行前端,最终 19 项后端测试全部通过,完整牌局流程跑通无异常。顺手清理了所有临时调试文件和没用的服务进程,项目结构清爽了不少,后面加新功能也省心。