脚本验证分几条线程:-par 的默认值与换算规则 图 1
脚本验证分几条线程:-par 的默认值与换算规则 · 图 1

同步一台节点的日志里,“验证区块”和”验证签名”是两个常被合并理解的词,把它们分开的正是 v31.0 源码参数表里的 -par=<n>:说明文字为 Set the number of script verification threads,取值规则写在同一句里——零代表自动,上限为十五,负数代表给系统留出若干核,默认值为零。这行参数管的是比特币核心给脚本验证分配多少条专用线程。

一、从参数值到线程数的换算

源码 src/node/chainstatemanager_args.cpp 里的换算只有几步:读入参数值后,如果小于等于零就加上本机 CPU 核数——零于是变成核数、负 n 变成核数减 n;再统一减一,源码注释解释:主线程本身就算在验证线程的名额里。于是三句话可以讲完语义:-par=0 自动取核数减一;-par=-2 留两个核给别的进程;-par=8 固定连主线程凑八条。上限方面,src/validation.h 的常量把专用线程数钳在十五以内,超过部分由源码里的 clamp 截断。还有一条容易被忽略的实现细节:验证队列按 128 个任务一批取活(src/validation.cpp 构造队列时写死的批次大小),线程数收益在批次调度粒度下会打折。

二、这些线程什么时候真正干活

专用线程服务的是区块连接阶段的签名校验:一个新区块带几百笔交易的签名验证任务,被切成批次投递给验证队列,多线程并行把这段 CPU 时间压下来;主线程继续做它不能并行的部分——维护状态、按序执行连接。这套分工决定了它的收益形状:验签是少数能安全并行的环节,其余步骤必须串行,所以线程堆到一定数量后曲线迅速趋平,源码把专用线程钳在十五条也正是这个道理。同步阶段之外,日常追链时这些线程多数时间在等待。与它互锁的另一半机制是检查点:-assumevalid 类参数覆盖的历史区块可以整段跳过逐笔验签,那部分工作无论线程多少都不发生——所以”加 par 加速同步”的收益集中在检查点之后的新链段。这也是它和钱包签名、RPC 线程完全无关的原因:这个队列只进区块验证任务。

三、取值时的三条判断

第一,默认零(自动)在绝大多数机器上是对的:核数减一已经把主线程与操作系统的调度余量算进去了,“全核”反而是外行的目标。第二,负值才是给共存负载留空间的正确写法——同一台机器跑数据库或多节点时,-par=-2 这种表达比写死小数值更稳,换机器部署时自动适配。第三,判断是否需要动它,看现象发生在哪一段:如果同步慢发生在验签段(CPU 逼近饱和、磁盘等待不高),线程数才有意义;瓶颈在磁盘或网络时,par 怎么调都只是移动等待的位置。GUI 的设置界面同样暴露了这个上下界——src/qt/optionsdialog.cpp 里滑块的最小值取负的核数、最大值取十五,与源码参数表同源,等于把同一句换算可视化。还有一处相邻参数值得放进同一张地图:-maxsigcachesize 控制签名缓存与脚本执行缓存各分多少内存,源码注释写明两者按上限的一半各取一半——线程把验签做快之后,缓存负责让重复验签不再发生,一组管吞吐、一组管重复,两条曲线一起看才能读懂节点的验证开销。

最后记一条边界:这些线程不改变验证的深度——多少核都只是把同一套共识规则的验证做快,从不跳过;能跳过旧验证的是另一族参数。理解 -par,本质是理解一台节点在”验证必须完整”前提下的算力分配问题。本文只讲机制,不构成任何投资或性能承诺。