资讯动态

第6篇(下)· 使用 PyQt5 实现 2048 高性能计算版:统一运行控制与跨模式安全交接

发布时间:2026/10/24 4:14:30 来源:尧图企业网站定制
第6篇下· 使用 PyQt5 实现 2048 高性能计算版统一运行控制与跨模式安全交接第 6 篇中让求解可以跑在独立 Worker 里并能暂停、继续、关窗恢复。但一旦标准 / 快速 / 后台求解三种执行方式同时存在新的问题就出现了用户暂停后换了运行方式点继续到底该恢复谁这一篇是 V6 三层能力中的状态层用一个状态机统一按钮、棋盘、RNG、最高分与历史并让暂停后跨模式继续成为一次可校验的安全交接而不是偷偷重启。篇目主题回答的问题第 6 篇上四类计算后端与公平基准同样的算法怎样算得更快且逐位一致第 6 篇中无头后台求解与三档运行模式怎样把长任务搬出 GUI并能暂停、继续、恢复第 6 篇下· 本文统一运行控制与跨模式安全交接三种执行模式并存时怎样保证状态与数据只有一个真相1. 问题三种执行模式带来的状态爆炸先看一个真实缺陷。旧版统一控制区里标准模式运行 → 暂停用户点快速选项卡界面显示已选中快速点继续。结果恢复的是标准模式。新选择没有生效只有等旧会话结束或用户停止后才会应用。根因不是按钮文案而是三处代码_v6_on_run_primary()在暂停态无条件调用恢复原会话后台BG_PAUSED → ctl.resume()本机_v6_local_paused → _v6_resume_local()完全不看_v6_selected_mode_v6_selected_mode只在_v6_apply_selected_if_idle()里被消费而这个函数只在无活动会话时调用暂停仍属于活动会话所以选择永远等不到生效根本不存在跨引擎交接路径本机暂停态没有把正式对局交给 Worker的流程后台暂停态也没有Worker 退出后再由本机继续的流程。于是切换运行方式在暂停态退化成改了一个没人读的变量。按钮为何不能只依据选中项显示当标准模式进入真正的已暂停状态后用户点击“后台求解”此时只改变目标选择尚未将棋盘交给 Worker。主按钮应显示“切换至后台求解模式并继续”点击后需要等待状态验证和所有权交接成功才可以显示“后台求解模式运行中”。如果只是把状态文字改成“后台运行中”却继续调用标准求解器就会重现本篇讨论的缺陷。2. 三个状态变量与真实执行模式V6 把状态拆成三个明确的变量v6_speed变量含义空闲运行中已暂停并改选_v6_speed当前生效/已选择的运行方式等于选择等于执行模式仍等于执行模式_v6_selected_mode运行中/暂停中用户点选的目标None目标或None目标_v6_active_mode真正在执行的模式Nonestandard/fast/background原模式但外部测试、启动期恢复、旧代码可能只设置其中一部分所以还需要一个推导函数def_v6_effective_active(self):activegetattr(self,_v6_active_mode,None)ifactiveinMODES:returnactiveifgetattr(self,_solver_running,False):spgetattr(self,_v6_speed,standard)returnspifspin(standard,fast)elsestandardctlgetattr(self,_v6_bg_controller,None)ifctlisnotNoneandctl.statein(HANDOFF_PREPARE,BG_RUNNING,BG_PAUSING,BG_PAUSED,BG_STOPPING):returnbackgroundreturnNone优先级是显式活动模式 → 本机求解器运行态 → 后台控制器活动状态。是否真的暂停也必须由执行模式自己回答def_v6_is_paused(self,activeNone):activeactiveorself._v6_effective_active()ctlgetattr(self,_v6_bg_controller,None)ifactivebackground:returnctlisnotNoneandctl.stateBG_PAUSEDreturn(self._solver_runningandself._v6_local_pausedandnotself._v6_local_pausing)本机的已请求暂停但还在等结果_v6_local_pausing不算暂停后台的BG_PAUSING同样不算。否则就会在数据尚未收敛时提前切换制造两个写者。3. 交互语义一张矩阵说清已暂停并选择快速主按钮变为“切换至快速模式并继续”统一控制区只有两个按钮主操作 停止状态全部由矩阵推导运行状态主按钮停止徽标运行中暂停可用XX模式运行中正在暂停正在暂停…禁用禁用XX模式正在暂停已暂停未变更模式继续可用XX模式已暂停已暂停已变更模式切换至XX并继续可用XX模式已暂停交接中正在切换至XX…禁用禁用正在切换至XX交接成功暂停可用XX模式运行中交接失败恢复原模式“继续”可用原模式已暂停 状态区失败原因三条硬规则徽标永远标识真实执行模式不按选项卡显示运行中选项卡只表达已选择不直接改变执行模式交接中禁用一切入口主按钮、停止、三个选项卡防止重复点击与并发创建 Worker。另外禁用态主按钮的文字也必须可读。旧配色是浅底浅字截图文字门禁直接判 0 对比度现改为#e3d8ca / #8a7b6c保证正在切换至…能被看清。4. 运行中切选项卡非破坏性运行中点击其它选项卡唯一允许的动作是记录选择def_v6_on_speed_clicked(self,key):ifself._v6_handoffisnotNone:return# 交接中禁止再次切换ifself._v6_session_active():self._v6_selected_modekey self._v6_sync_run_controls()returnself._v6_selected_modeNoneself._v6_apply_tier(key)# 空闲时才直接生效这样做的原因很实际运行中切档如果立即生效就必须暂停/重启当前会话而用户点选项卡的意图往往只是待会儿换不是现在中断。把选择和执行分开按钮才有机会表达切换并继续这个第四种状态。5. 安全步边界暂停本机档的暂停不是立刻停而是当前这一步算完再停没有在途计算立即进入_v6_local_paused有在途计算置_v6_local_pausing等结果返回时暂存它_v6_paused_result不落子_request_decision()在暂停态直接返回不会再请求下一步。def_on_solver_result(self,job_id,result):if(self._v6_local_pausingandself._solver_runningandjob_idself._job_id):self._solve_in_flightFalseself._v6_paused_result(job_id,result,self._board_sig)self._v6_local_pausingFalseself._v6_local_pausedTruereturnsuper()._on_solver_result(job_id,result)为什么保留这个结果因为它是暂停那一刻已经算出来的下一步。同模式继续时应该直接用它而不是重算——重算会浪费一次搜索也可能因为 RNG 消费差异导致与暂停点不一致。同模式继续继续时先校验job_id与棋盘签名命中则直接应用暂存结果否则重新请求决策。6. 跨模式交接三条路径交接只在真正暂停后发生。_v6_handoff {from,to,phase}一旦非空所有操作入口直接返回。6.1 标准 ↔ 快速同一会话换调度这是最轻的一条路径不重建会话只换动画/粒子/步间等待def_v6_handoff_local_to_local(self,src,dst):self._v6_paused_resultNone# 旧模式暂存结果失效self._v6_local_pausingFalseself._solve_in_flightFalseself._pending_resultNoneself._job_id1# 旧任务身份作废self._clear_hint()# 旧方向提示不属于新模式self._v6_apply_tier(dst)# 动画/粒子/等待/偏好self._v6_active_modedst self._v6_local_pausedFalseself._v6_handoffNoneself._request_decision()# 从当前棋盘继续关键点不重新开局、不重复落子_session、棋盘、分数、撤销栈都保持旧的暂存结果被丢弃并递增job_id即使旧任务的结果迟到也会被忽略方向提示先清除避免新模式展示旧方向标准恢复动画与合并粒子快速关闭动画与粒子两者都保留方向提示。6.2 本机 → 后台先确认接管再结束本机交接中主按钮“正在切换至后台求解模式…”操作禁用错误做法是先停本机再启动 Worker。一旦 Worker 启动失败原暂停会话已经没了用户既不能继续也不能恢复。V6 的顺序相反从当前一致状态构建快照棋盘、分数、最高分、完整 RNG、正式 game_id、交接元数据ctl.start(snapshotsnap, handoff_metameta)控制器轮询owner.json确认alive且 pid 与子进程一致才回调_v6_on_bg_started()此时才静默结束本机会话_stop_solver(..., silentTrue)并应用后台档效果。def_v6_on_bg_started(self,meta):ifmeta.get(from)in(standard,fast):self._v6_stop_local(交由后台求解接管,silentTrue)self._v6_apply_tier(background)self._v6_handoffNoneself._v6_sync_run_controls()如果启动失败_v6_on_bg_start_failed()只清交接标记、恢复选项卡勾选原暂停会话与检查点保持不动状态区明确显示后台求解模式切换失败原会话保持暂停。6.3 后台 → 本机等旧写者真正退出后台暂停并选择标准可安全切回本机后台暂停时Worker 只是停在步边界进程仍常驻并持有所有权。直接启动本机求解器会造成两个写者。正确顺序是校验BG_PAUSED并确认 FINAL 已采纳棋盘/分数不一致时补一次正式回放drain_for_handoff()发送quit等待进程已退出且owner 非 alive清空旧会话身份_game_idNone、_final_seenTrue让迟到消息无法再被采纳应用目标档位再启动本机求解器。defdone(ok,reason):ifnotok:self._v6_handoff_fail(reason,src,dst)returnctl._game_idNonectl._final_seenTrueself._v6_active_modeNoneself._v6_handoffNoneself._v6_apply_tier(dst)self._v6_start_local()三条路径的前置、顺序与失败回退可以压成一张表IPC 细节见第 6 篇中路径前置关键顺序失败时标准 ↔ 快速本机已暂停丢弃暂存结果 →job_id→ 应用档位 → 从当前棋盘请求决策同步执行无 Worker 失败路径本机 → 后台本机已暂停建快照 → 启动 Worker → 确认 owner/pid → 结束本机会话保留本机暂停会话与检查点后台 → 本机BG_PAUSED且 FINAL 已采纳发quit→ 等进程退出 owner 释放 → 启动本机保留后台暂停与检查点这三条路径的共同原则是先让新执行者拿到合法身份再让旧执行者退出任何一步失败都保留原暂停会话。7. 单一写者为什么进程起来了不等于接管了后台启动时控制器不会在Popen成功后立即宣告运行中而是进入HANDOFF_PREPARE轮询所有权stown.owner_status(self._staging_dir)recst.get(record)or{}ifst.get(state)aliveandint(rec.get(pid,-1))int(proc.pid):self._confirm_running(meta)returnifproc.poll()isnotNone:self._fail_start(后台进程在接管所有权前退出exit%s%proc.poll(),meta)returniftime.monotonic()deadline:self._fail_start(等待后台 Worker 接管所有权超时owner%s%st.get(state),meta)returnQTimer.singleShot(50,lambda:self._await_ownership(deadline,meta))这解决两个问题Worker 可能启动即失败导入错误、端口/权限问题此时 GUI 不能进入运行中旧 Worker 可能还持有 owner直接起新进程会变成并发写者。只有确认接管后控制器才迁移到BG_RUNNING并写入一份交接记录{from:fast,to:background,model_game_id:20261020_054301_098475,session_id:20261020_054305_040_bg,board_sig:[[0,0,2,2],[0,4,8,16],[4,64,128,256],[32,512,1024,2048]],score:768,best_score:62188,pid:38632,at:2026-10-20 05:43:05}新执行会话可以有新的session_id但必须通过model_game_id与正式对局建立明确的前后关系。8. 数据一致性四个不许变跨模式交接必须保证四类数据不变下文 E2E 中的最高分62188是固定测试盘面使用的夹具值用于验证“只升不降”不是真实玩家纪录数据约束实现历史最高分只来自权威模型永不被 Worker 快照覆盖渲染最高分时读model.best_score忽略msg[best_score]正式game_id交接不换局快照与交接记录都携带正式 game_id后台 session 另起 ID撤销/重做栈与正常对局一致FINAL 回放官方模型而不是覆盖棋盘RNG暂停点逐位一致完整getstate()交接FINAL 回放后再校验最高分这一条曾经真实出错Worker 回传里best_score score 5004界面把当前分数显示成了历史最高分。修复方式不是改文案而是改数据源self.win._render_score(self.win.best_label,最高分,int(getattr(self.win.model,best_score,0)))迟到消息同样要挡两层已收尾会话的final_final_seenTrue且状态已回到空闲直接丢弃game_id与当前会话不匹配的final直接丢弃。progress原本就有revision 不递增不渲染game_id 不匹配丢弃两道门禁。否则旧 Worker 的最后一帧可能在交接完成后把棋盘改回去。9. 失败与回退保留可恢复的暂停交接失败不是恢复到错误模式而是什么都不改本机 → 后台失败本机会话从未被停止仍停在暂停点后台 → 本机失败例如所有权释放超时后台仍停在BG_PAUSED检查点保留选项卡勾选恢复到真实执行模式状态区写入失败原因不自动重试、不静默降级交由用户决定再次切换或直接继续。这比尽力恢复更重要一个错误的自动恢复会同时破坏数据与信任。10. 真实 GUI E2E22 项检查后台退出、所有权释放后由标准模式继续验证不是只跑单元测试而是用真实窗口 真实 Worker 走完整条链路阶段动作关键断言A标准真实运行 → 安全暂停activestandard、按钮“继续”B切快速继续activefast、anim_offTrue、job1、棋盘/最高分/game_id 保持C切后台继续跑到 217 步owneralive 且 pid 匹配暂停后官方棋盘/分数与 Worker 终态一致RNG 逐位一致D后台暂停 → 标准继续Worker 退出、ownerabsent本机只启动一次撤销栈与最高分不回退E再切快速连续 200 步累计 419 步≥400最高分全程 62188检查项22/22 通过进程退出码 0原始证据v6_pyqt_2048_accelerated/experiments/v6_2/20261020_cross_mode_handoff/cross_mode_e2e.jsonoktrue、checks22、失败 0与同目录cross_mode_e2e.log截图8 张真实桌面整窗图含暂停已选择、切换后运行、后台 200 步、后台暂停、切回标准等两轮中间取证被保留第一轮暴露未确认所有权的时序问题第二轮用 trace 证明暂停确实冻结本机求解异常来自测试盘面过快终局后台 1 秒内跑到 303 步 game over而不是交接逻辑。11. 测试矩阵8 种暂停切换 4 项扩展tests.test_v6_cross_mode用 15 项测试覆盖矩阵#原模式已暂停目标操作结果1标准标准继续PASS2标准快速切换至快速模式并继续PASS3标准后台切换至后台求解模式并继续PASS4快速标准切换至标准模式并继续PASS5快速后台切换至后台求解模式并继续PASS6后台标准切换至标准模式并继续PASS7后台快速切换至快速模式并继续PASS8后台后台继续PASS扩展项运行中连续切换选项卡执行模式不变只记录选择交接中重复点击/停止/再次切档只允许一次ctl.start后台启动失败、所有权释放超时原会话保持暂停并提示原因200 步连续落子 交接最高分不回退、RNG 状态逐位保持迟到final含已清空game_id的情况不得覆盖官方棋盘。全套 V6 测试共136项12 个模块0 失败、3 跳过。12. 门禁与未验证项门禁结果P0 根因定位可复现、可证伪PASSP1 统一交互语义PASSP2 标准 ↔ 快速交接PASSP3 标准/快速 ↔ 后台交接单一写者PASSP4 按钮/文案矩阵PASSP5 数据正确性PASSP6 8 种暂停切换 4 项扩展PASSP7 unittest 真实 IPC E2EPASS退出码 0截图 CAPTURE / STRUCTURE / TEXTPASS5/5 场景截图像素级人工目视VISUAL_PASSUNVERIFIEDVISUAL_PASS必须由人工核对截图后授予自动门禁只覆盖结构、文字与捕获完整性未核验不标记 PASS。13. 小结V6 的最后一层不是再加功能而是把三种执行方式收进同一个状态机选择与执行分离选项卡只记录意图主按钮才改变执行暂停必须真实正在暂停与已暂停是两种状态前者禁止切换交接先确认再收尾本机 → 后台先确认 Worker 接管后台 → 本机先确认 Worker 退出数据只有一个真相最高分、game_id、撤销栈、RNG 不被快照覆盖迟到消息有门禁失败保留退路任何交接失败都回到原暂停状态而不是自动猜一个模式。到这里V6 的三层能力闭环计算层保证算得快且算得对执行层保证长任务可控可恢复状态层保证换执行位置时数据不漂移。上一篇第 6 篇中· 使用 PyQt5 实现 2048 高性能计算版无头后台求解与三档运行模式本系列至此完结系列共 6 篇、9 个文件。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑