Skip to content

多会话并发写入与乐观工作区写锁 ​

在同一个项目工作区中并行运行多个智能体会话时,piwin 如何在互不阻塞的前提下,可靠防止代码交叉覆盖,并提供可信的变更追踪与撤销保障。


1. 背景与并发挑战 ​

在典型的 AI 编程场景中,开发者往往希望在同一个项目里同时进行多项任务:

  • 会话 A 正在执行耗时数分钟的端到端测试或构建任务;
  • 会话 B 正在进行全局代码检索与语法分析;
  • 会话 C 需要对具体的业务逻辑模块进行小幅修复。
text
会话 A:  pnpm test  ████████████████████████████████ (耗时较长)
会话 B:  rg "pattern" ▌ (检索,仅需数十毫秒)
会话 C:  edit_file    ▌ (修改单文件)

传统全局排他锁的缺陷 ​

最保守的并发策略是对整个工作区加全局互斥锁:只要有任何终端命令在运行,其他所有会话均必须完全挂起等待。这种设计虽然安全,但在实际开发中极易引发严重的连锁阻塞。一个正在运行测试的后台会话,会导致其他会话连最基础的代码查看、文件搜索(如 rg)甚至分支状态检查(git status)都无法执行,界面卡顿且响应迟缓。

业界的折中方案 ​

  • 物理隔离(Worktree / 沙箱):为每个并发会话分配独立的 Git Worktree 或远端容器。隔离彻底,但在小步快跑的轻量编辑场景下,频繁创建、拉取与合并分支的开销过大。
  • 完全放任(无锁与简单时间戳):会话之间完全不加互斥控制,仅在写文件前校验修改时间戳。由于终端命令对工作区文件具有任意修改能力,这种模式极易发生静默覆盖与脏写。

piwin 已经原生支持基于 Git Worktree 的物理隔离会话。在此基础上,为了满足多会话在**同一工作区检出(Checkout)下流畅结对协作的需求,piwin 引入了乐观工作区写门禁(Optimistic Workspace Write Gate)**机制:以乐观并发为基础,通过细粒度租约、精确局部修改以及实时的交叉冲突检测,兼顾吞吐效率与写入安全。


2. 租约模型:细粒度的并发协调 ​

工作区写门禁以工作区的物理根路径为基准统一协调所有文件写入、删除与终端命令。根据操作的影响范围,系统将并发申请划分为三种租约类型:

租约类型对应操作互斥范围行为与特性
工作区共享租约只读命令、普通编译与测试 Shell 指令仅与全局排他租约互斥多个会话可同时并行运行,不抢占任何文件写入权。
共享 + 单文件独占租约edit、write_file、delete_file、move_file、move_lines(后两者一次锁住涉及的两个文件)与修改同一文件的其他写入互斥;与全局排他租约互斥操作不同文件的会话完全并行,仅对目标文件施加互斥锁。
工作区排他独占租约依赖安装、跨分支切换、全仓代码格式化与当前工作区的所有其他操作互斥具有全局破坏性影响的操作,必须独占整个工作区。

此外,当用户执行按轮撤销或回滚变更时,系统会申请目标轮次涉及文件的独占租约,并在毫秒级完成内容核验与安全回退。


3. 全局排他命令的界定与调度 ​

并非所有的终端命令都需要全局加锁。系统建立了一套严格的黑名单过滤规则,仅对会对全仓文件造成不可逆改动的高危指令施加全局独占:

独占命令范畴 ​

  1. 工作区级 Git 版本操作:git checkout、switch、reset、restore、clean、stash、rebase、merge、pull、cherry-pick 等。
  2. 包管理器与依赖安装:pnpm install/add/remove、npm ci/install、yarn add 等。
  3. 全仓批量格式化与修复:带有 --write 或 --fix 参数的 Prettier、ESLint、Biome 等工具。

关于 Git 暂存与提交操作
git add 与 git commit 不属于排他命令。它们仅更新 Git 暂存区与索引,Git 原生自带的 index.lock 机制已具备足够的原子串行保护,无需额外阻塞整个工作区。

有限读优先:防排队饥饿机制 ​

在先进先出(FIFO)队列中,如果一个全局排他命令(如依赖安装)排在某个长期测试任务后面,后续所有到来的轻量级只读请求(如文件搜索、内容查看)都会被排他命令阻塞,导致队列全面瘫痪。

为了解决这一问题,系统实现了有限插队窗口机制:

  • 当全局独占任务在队列中等待前序任务释放时,系统允许后续到达的轻量共享请求在前 20 秒窗口期内直接插队并行执行;
  • 一旦独占任务的等待时间达到 20 秒上限,插队窗口即刻关闭,后续所有请求必须按序排队,确保独占任务不会遭遇持续饥饿。
text
t=0s   会话 A: pnpm test ──────────────────────────────▶ (持有共享租约运行)
t=1s   会话 B: git checkout ··· 排队等待(排他请求)
t=5s   会话 C: rg "search" ▌ (处于 20s 保护窗口内,允许直接插队执行)
t=25s  会话 D: ls ············ 排队进入队列尾部(窗口已关闭,优先保障独占任务)

4. 冲突感知与事后状态标注 ​

在乐观并发模式下,即使多个会话同时执行看似互不干扰的操作,底层文件仍可能在执行周期内发生细微交叉。系统通过轻量化的流水追踪与状态指纹,让智能体对环境变动保持准确感知。

1. 逻辑时钟与活动流水 ​

工作区内部维护单调递增的逻辑时钟。每当有工具或终端操作获取、释放租约时,系统均会记录其会话标识、影响文件及生效时间段。任何检测只需发起简单查询:“自本操作启动以来,是否有其他会话在当前工作区进行过修改?”

2. 终端执行结果的交叉变更标注 ​

当一个长时间运行的普通 Shell 命令结束时,系统会自动比对其运行区间内其他会话的活跃记录:

  • 无任何文件改动:保持静默,不对命令输出做任何额外修饰,消除信息噪音;
  • 其他会话产生了文件变更:系统借助轻量级状态指纹(文件状态与属性校验)识别脏文件范围,并在终端输出尾部自动附加环境上下文提示:
text
[workspace] Other sessions were active in this workspace while this command ran,
so its result may not match the current files.
Written by other sessions: src/api.ts
Changed during the run: src/api.ts, src/api.test.ts
Re-run the command if you need a result for the current files.

命令的执行不会被强制中断,但模型能够明确知晓测试或检查结果可能对应于中间态版本,并在必要时自主重新评估。

3. 整文件覆写的前置版本校验 ​

write_file 会整体替换文件内容,是并发协作中最容易冲掉他人成果的操作。系统会为每个会话记录写入成功时的文件内容哈希。

当某会话再次尝试整文件覆写时:

  • 若目标文件已被其他会话修改,系统将主动拒绝首次覆写,并明确提示模型重新读取最新内容、合入上下文后再行保存;
  • 若目标文件变动仅源自会话自身的格式化工具或当前会话的操作,则自动放行。

5. 原子局部编辑:高并发下的安全基石 ​

传统编码助手中,模型由于缺乏局部编辑能力,经常通过 Shell 脚本(如 sed -i 或 Python Heredoc)对文件进行局部修改。终端脚本对宿主系统完全黑盒,无法进行粒度锁控制,更容易破坏变更审计链路。

为了提供高并发下的安全编辑体验,系统提供了原生原子局部修改工具 (edit):

json
{
  "path": "src/api.ts",
  "edits": [
    {
      "oldText": "const TIMEOUT_SECONDS = 30;",
      "newText": "const TIMEOUT_SECONDS = 60;"
    }
  ]
  }

并发安全性保障 ​

  • 单文件锁内的磁盘实时匹配:文本的查找与替换完全在目标文件的排他锁内执行,直接面向磁盘当前最新内容。
  • 并发改动天然并存:两个会话分别修改同一文件的不同函数时,两次文本替换均能稳定应用,完美保留彼此的改动。
  • 严格精确匹配:坚持字符级严格匹配,杜绝宽泛模糊替换,彻底避免因行尾空格或引号差异误改他人刚刚更新的代码。
  • 原生格式保护:自动识别并保留原文件的 Byte Order Mark (BOM) 与 CRLF/LF 换行风格。

6. 按轮变更审计与安全撤销 ​

Host 把每个提示词执行尝试中用文件工具记录的写入合成为本轮变更集(Turn ChangeSet)。多文件操作并非文件系统原子事务:逐路径核验、写前备份、写后验证,失败时安全回退或进入需要修复状态(ADR 0083)。撤销只由用户点击或 CLI 明确命令授权,不注册为模型工具。

1. 独立归因与精细化展示 ​

工具卡片统计该次调用本身的补丁,回答下方汇总则统计这一轮净变更,都不拿 Git HEAD 的全部差异冒充本轮贡献。文件直接列在「本轮改动 · N 个文件 · +A −D」卡片中,点文件看历史 diff;右侧「本轮变更」与「工作区全部 Git 变更」明确分开。

每条 shell 执行前后取指纹:只读命令不影响完整性。命令改了的文件,Host 在命令前把当时已脏的文件存入撤销对象库(按大小+修改时间缓存,一个文件版本只读一次),命令后读取新字节;前像取命令前快照,干净文件取 HEAD 里的内容(HEAD 里没有的文件视为不存在)。这样命令改的文件和文件工具改的一样进入本轮变更集、可撤销,命令与文件工具改同一文件也能串成一条链。字节最终没变的(touch、只是提交了本轮的改动)不算改动。会移动 HEAD 或索引的 git 命令(checkout、reset、stash、merge…)的文件变化不取像;符号链接、超大文件、超出单命令上限(500 个文件 / 128 MB)、HEAD 可能与工作区字节不一致的文件(eol/LFS 等过滤)同样只按路径记录:没和本轮文件工具改动重叠的,列为「另有 N 个文件由命令修改」,撤销不动它们;有重叠、无法审计(非 git 工作区)时,该轮不能自动撤销,卡片只列出重叠的文件。暂停续跑一般追加到同一变更集;已撤销后继续则开新变更集,旧的仍可恢复。

2. 安全门禁与异常出口 ​

  • 一键「撤销」→「本轮已撤销」→「恢复改动」,不弹常规确认,不删除聊天/分支/草稿,不自动启动模型或测试。
  • 所有目标文件仍须匹配记录字节和存在性。有后续变化就整次零写入拒绝:「为保留后续改动,本次未修改任何文件」。唯一的例外:只由命令新建(前像是「不存在」)的文件,本轮之后又被改动(后台 dev server、测试日志等)时,撤销不再整次拒绝,而是把它留在原处、撤销其余文件,并在结果里列出这些文件(skippedPaths,卡片显示「有 N 个命令新建的文件在这一轮之后又被改动,撤销没有动它们」);已经不存在的这类文件直接略过;若本轮只剩这类文件且都变了,仍按拒绝处理。恢复改动时同样不碰被留下的文件。Host 工具写的文件、命令修改的已有文件仍严格核验。点「查看冲突」在右侧查看受阻路径、已知后续轮次(同会话可定位)或「来源未知」及历史/当前差异;不猜测来源、不级联、不强制覆盖。处理后点「重新检查」,不会自动执行撤销。
  • 受影响路径已暂存或未合并时拒绝;无关暂存项、index、HEAD 均不动。HEAD 移动本身不阻止仍匹配记录的文件撤销。
  • 核验/备份阶段可取消;首写后只安全完成或回退。进度为 N / M 个已验证文件。断线显示「结果尚未确认」,重连以相同命令和幂等键重取持久结果,不生成新的操作。
  • 回退失败进入 needs-repair:挡住相关路径的 Host 文件工具以及重叠工作区的 Git/集成操作,重启也保持;普通 shell、MCP 和外部编辑仍属乐观边界,不是 OS 沙箱。修复先预览、确认,只恢复仍为该操作内容的文件,不覆盖外部新内容;全部回到操作前内容后验证解除阻塞。
  • 下一次受影响会话执行/继续时,模型收到已撤销/恢复的文件事实,要求重新读取。原回答和测试结果仍属历史,不自动重跑。

3. 备份、保留期与多端 ​

右侧 Git 视图的「更多 → 代码撤销记录」提供操作查询、恢复和修复。修复详情可「导出备份」到工作区外的新 piwin-undo-backup-<operationId> 目录(含 manifest),不直接覆盖项目。目标父目录必须已经存在,填写 Host 上的绝对路径;本机 sidecar 可用目录选择器,远程 Host 手动输入。

轮次结束 30 天后数据过期,摘要保留;近期撤销/恢复至少延长 7 天;进行中/需修复数据不清理。5 GiB 软预算、单对象 20 MiB 上限:不能保存新撤销数据时仍完成原任务,但标「撤销存储已满,未保存」。不会为腾空间删受保护数据。永久删除会话会提示:撤销记录/备份属于工作区,按保留期保留,不随聊天删除。

Mobile 只读摘要和状态(已撤销、记录不完整、需要桌面修复、过期、正在撤销/恢复、存储已满),没有写入口。Desktop 与 CLI 共用同一个 Host 服务:

bash
piwin turn show <sessionId> <runId...>
piwin turn undo <changeSetId> --expected-version <n>
piwin turn redo <changeSetId> --expected-version <n>
piwin turn operations [projectPath]
piwin turn operation <operationId>
piwin turn cancel <operationId> # 首写前有效
piwin turn repair <operationId> --expected-version <n> [--yes] # 默认预览
piwin turn export <operationId> <destination>

CLI 拒绝/回退/取消以非零状态退出,冲突逐路径列出后续变更集与会话,未知来源明确写 unknown。远程导出建议使用绝对路径;相对参数按 CLI 当前目录转成绝对路径后交 Host 校验。

尚未覆盖:模式位/仅 executable 位变化没有完整回执与还原保证(删除后重建可能丢失原模式);按单次工具调用撤销、移动写入口、容量管理界面和完整跨端视觉矩阵另列 backlog D-WWG-08/09。


7. 并发行为保证与场景速查 ​

典型并发场景系统处理行为预期结果与保证
会话 A 运行测试,会话 B 进行搜索/读取授予共享租约完全并行,无等待,无提示干扰。
会话 A 运行测试,会话 B 编辑文件共享租约与单文件独占租约并存编辑立即生效;会话 A 测试完成后自动标注“运行期间文件被修改”。
两会话同时编辑同一文件的不同区域依次进入单文件排他锁执行局部替换均顺利生效,各自改动完整保留。
两会话同时编辑同一文件的相同行先到者成功,后到者匹配文本失效后到者安全报错,被引导重新读取最新内容后再修改,绝不静默覆盖。
会话 A 覆盖写入已被会话 B 修改的文件触发写后写版本过期检测系统拒绝写入,提示模型拉取最新内容进行合并。
会话 A 运行长命令,会话 B 执行切换分支独占请求进入等待队列会话 B 排队等待会话 A 结束(享有 20 秒插队保护窗口)。
撤销已被后续会话修改的某一轮操作触发撤销前置哈希校验整体拒绝撤销,明确指明冲突文件,杜绝破坏最新代码。

8. 最佳实践建议 ​

  1. 日常结对开发(共享单一 Checkout)
    在绝大多数场景下,直接在默认工作区内开启多个会话即可。利用乐观写锁与细粒度 edit,不同会话能够协同编写逻辑、补充单元测试或重构函数,兼顾轻快与安全。
  2. 重度架构改造与互斥功能开发(启用 Worktree 物理隔离)
    当需要同时进行不兼容的深层次重构、切换大版本分支或涉及大批量底层依赖升级时,推荐为会话创建独立的 Git Worktree。在物理隔离的工作区之间,所有锁机制与操作流水完全独立,互不影响。

Released under the MIT License.