一个看起来需要串行的问题
这次遇到的问题很具体:我们做了一个本地素材库,想确认它会不会自动启动剪辑软件;与此同时,另一个企业接口也要取数据,能不能和素材库查询一起跑?表面上看,这些事情都和剪辑有关,似乎只要出现同一个软件的名字,就应该排队执行。
我一开始也理解成了一个“会不会互相抢资源”的问题。但如果只凭名称判断,答案很容易偏掉。素材库、目录索引、企业接口,可能只是调用入口不同,也可能背后共享同一个进程、同一把锁或同一个写入中的数据库。真正要确认的,不是它们是不是同属剪辑工作,而是它们到底沿哪条路径取数据。
并发能不能成立,不能看功能名称是否相近,要看实际数据源、读写方式和共享状态。
如果不处理这个问题,后面会出现两种相反的麻烦:该并发的查询被迫一个个排队,整体速度变慢;不该并发的写入被同时启动,索引库或编辑流程出现冲突。更麻烦的是,偶尔成功一次会制造假象,让人以为这个设计始终安全。
先追入口,再谈剪辑软件
这次排查没有停在命令名称上,而是从素材库查询入口一路往下追。入口最后落到本地的 SQLite 索引库,打开方式是只读,查询过程只读取已经建立好的索引,不会因为搜索而写回索引库。也就是说,素材库查询虽然服务于剪辑工作,但它本身并没有把剪辑软件拉起来作为前置条件。
这一步很关键。很多系统会把“查询素材”和“操作剪辑”放在同一个产品里,于是使用者自然会认为查询等于进入编辑流程。实际上,一个产品里的功能可以经过完全不同的执行路径:有的路径只是读缓存,有的路径才需要连接正在运行的编辑器。
我选择把素材库查询归为独立的读取流程,是因为证据指向本地只读数据库,而不是启动编辑器后再询问它。既然查询不依赖编辑器窗口、不需要编辑器返回当前状态,就没有理由把它和建稿、配音或导出流程绑在一起等待。
目录索引不是编辑操作
本地素材目录索引又是另一条路径。它做的事情主要是扫描目录,再调用媒体信息探针读取文件的时长、格式等信息。这里可能有磁盘访问,也可能因为素材数量多而耗时,但它没有调用编辑器窗口,也不需要进入剪辑流程。
这让我重新区分了“访问素材”和“编辑素材”。前者可以只是读取文件系统元数据,后者才可能改变工程、缓存或应用状态。两者都和视频文件有关,却不共享同一种风险。把它们都叫“素材相关操作”,并不能说明它们必须串行。
当然,目录索引并不是完全没有边界。如果另一个流程正在移动、删除或重命名同一批文件,索引结果就可能出现短暂不一致;如果索引任务本身要写入索引库,也不能再把它当作普通查询。只读查询可以并发,不等于所有“素材库相关动作”都可以随意并发。
所以我没有用“它不启动剪辑软件”作为唯一结论,而是继续检查它是否写库、是否拿共享锁,以及是否依赖某个进程内状态。并发判断需要同时满足这些条件,少看一项都可能把例外当成规则。
企业接口未必真的在访问在线服务
问题里提到的企业接口,还容易让人产生另一个误会:只要名字里有“接口”,就一定要连到远端服务。沿调用链核对后,已确认的取数路径并不直连在线服务,而是读取剪辑软件留下的本地缓存数据库和本地索引,仍然以只读方式打开。
这改变了问题的重点。远端接口通常要考虑限流、连接数、服务端状态和请求顺序;本地缓存查询则首先要考虑文件锁、数据库读写模式和缓存是否正在刷新。如果把“接口”这个词直接等同于“在线请求”,就会把真正需要检查的地方放错。
我判断这条取数路径可以和素材库查询并发,是因为它们都没有启动目标应用,也没有拿同一把剪辑流程锁;已核对的路径还是本地读取,而不是要求编辑器在前台保持某种状态。它们之间最多存在普通的磁盘读取竞争,不能因此推导出必须串行。
但这里也有一个必须保留的边界:刷新索引不是查询。刷新动作会写入 SQLite 输出,同一时间只应该运行 1 个刷新任务。查询可以在刷新期间继续读取,具体是否能看到最新内容要看数据库和缓存的更新时机;这属于数据新鲜度问题,不等于编辑器冲突,也不该被混成同一个结论。
最容易误判的两个捷径
第一个错误捷径,是看到两个功能都和剪辑有关,就默认它们必须串行。这个判断把产品归属当成了资源归属,却没有问它们是否共享进程、共享锁或共享写入目标。它可能让本来安全的读取流程排长队,也让真正危险的写入流程被错误地放行。
第二个错误捷径,是只看命令名里有没有“接口”,或者只看它是不是一个本地脚本。命令名只能说明调用者怎么称呼这项能力,不能说明它最后读了什么、写了什么。一个叫接口的入口可能只查本地缓存;一个看起来只是查询的入口,也可能在第一次运行时刷新索引。
判断并发安全性的最小单位不是命令,而是一条完整的查询路径。
这件事让我重新意识到,工具名称往往是给人记忆的,锁和数据源才是给系统决定边界的。名字相近的动作,可能完全可以并发;名字不同的动作,也可能因为写同一个库而必须排队。
一张足够小的并发检查清单
以后遇到类似问题,我会沿调用链核对 4 件事。第一,数据源是本地文件、本地数据库,还是远端服务;第二,打开方式是只读,还是包含写入、刷新或迁移;第三,执行过程中是否会启动目标应用,或依赖某个进程窗口里的状态;第四,是否会拿共享流程锁,或者和别的任务写同一份输出。
这 4 件事不需要一开始就把整个系统读完。先看入口,再看实际打开数据库或文件的地方,最后确认是否有启动应用和加锁动作,通常就能判断大部分边界。只读、本地、无共享锁、无进程状态依赖的路径,可以作为并发候选;只要出现写入、刷新、移动文件或共享状态,就要进一步确认,必要时退回串行。
我会把结果分成 3 类,而不是只回答“能”或“不能”。本地只读查询属于可并发;同一索引库的刷新属于单任务写入,但可以和查询分开处理;涉及编辑器进程、工程变更、导出或其他共享流程锁的动作,则按串行边界管理。这样写出来,执行者知道哪些能同时跑,也知道哪些动作不能从“查询安全”推导出来。
一个流程成熟的标志,不是它能在顺利时并发跑通一次,而是它能在下一次遇到刷新、文件变动或应用状态变化时,迅速找到该停在哪里。并发不是把所有任务同时扔出去,而是把真正不共享状态的路径分开。
下次我会怎么做
下次再有人问“这两个能不能并发”,我会先把问题改写成 4 个更小的问题:它读哪里?是否写入?会不会启动应用?是否拿共享锁?如果答案都清楚,再决定并发;如果有一项不清楚,就先把那一项查实,不用凭产品名称猜测。
具体到本地素材库,我会让查询、目录扫描和缓存取数保持各自独立,把索引刷新和编辑流程单独标出。查询可以并发,不代表刷新可以多开;不启动编辑器,也不代表可以忽略同一批文件正在移动或删除的情况。每个结论都要带上适用范围,避免一句“可以并发”被误用到所有动作。
这次真正留下来的,不是某个功能的特殊答案,而是一种更稳的排查顺序:先找实际数据源,再确认读写模式,然后排除进程状态和共享锁,最后才给出并发建议。表面上我是在判断几个素材工具能不能同时运行,其实我是在给工作流划出不会互相惊扰的边界。这个边界越清楚,后面就越少依赖临场记忆,也越不容易把一次成功误认为永久安全。