You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jenkins脚本式语法parallel阶段中for-each循环为何运行异常?

Jenkins脚本式Pipeline parallel循环构造任务结果异常问题

问题复现代码

List<String> strs = ['foo', 'bar', 'baz']

Map parallelStages = [:]
for (s in strs) {
    String stageName = "print $s"
    parallelStages[stageName] = {
        stage(stageName) {
           println "Hello, $s"
        }
    }
    
}
parallel parallelStages

实际运行输出

Hello, baz
Hello, baz
Hello, baz

预期运行输出

Hello, foo
Hello, bar
Hello, baz

现象说明

  • 注释掉parallel parallelStages代码行后,该代码片段可正常运行
  • 测试打印parallelStages映射内容,返回结果为{print foo=org.jenkinsci.plugins.workflow.cps.CpsClosure2@38d330f7, print bar=org.jenkinsci.plugins.workflow.cps.CpsClosure2@30e6a23b, print baz=org.jenkinsci.plugins.workflow.cps.CpsClosure2@6abe446},映射的键名完全符合预期

运行环境

  • Jenkins: 2.332.2
  • Pipeline plugin: 2.7

补充说明

该问题可在原生Groovy的闭包循环场景下复现,不属于Jenkins Pipeline专属问题。

问题根因

这是Groovy语言闭包的延迟变量绑定特性导致的经典问题,和Jenkins CPS流程引擎本身无关:

  1. for-each循环中定义的s、stageName属于循环外作用域的同一个变量引用,不是每次迭代生成独立的块级变量
  2. 构造闭包存入parallelStages时,闭包不会立刻捕获变量当前的值,只会保留对变量本身的引用
  3. 等循环执行完成,s的值已经固定为遍历的最后一个元素baz,stageName也固定为最后一次赋值的内容
  4. 后续执行parallel调用所有闭包时,所有闭包访问的都是同一个已经跑完循环的变量,因此全部输出Hello, baz

注释掉parallel行后结果正常,是因为单线程顺序执行时闭包会在循环迭代的当下立刻执行,变量值还没被后续迭代修改,所以看起来符合预期。打印Map时键值正确,是因为Map的键是在循环赋值时就立刻求值存入的,不存在延迟绑定。

修复方案

核心思路是让每个闭包捕获独立的、不会被循环修改的变量副本,两种常用写法:

方案1:每次迭代生成独立的局部变量副本

在循环内部用临时final变量固定当前迭代的值,闭包捕获这个不会变的局部变量即可:

List<String> strs = ['foo', 'bar', 'baz']
Map parallelStages = [:]
for (s in strs) {
    // 定义当前迭代专属的final变量,值固定后不会被后续循环修改
    final String currentStr = s
    final String currentStageName = "print $currentStr"
    parallelStages[currentStageName] = {
        stage(currentStageName) {
            println "Hello, $currentStr"
        }
    }
}
parallel parallelStages

方案2:使用Groovy的collectEntries方法直接构造并行Map

借助集合类的迭代方法自带的块级作用域,天然规避变量引用问题,写法更简洁:

List<String> strs = ['foo', 'bar', 'baz']
Map parallelStages = strs.collectEntries { s ->
    String stageName = "print $s"
    [
        (stageName): {
            stage(stageName) {
                println "Hello, $s"
            }
        }
    ]
}
parallel parallelStages

两种写法运行后都可以得到预期的输出结果。

内容的提问来源于stack exchange,提问作者ThomasMX

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 18:36:25