《使用 testing/synctest 测试并发代码》

原文链接:《使用 testing/synctest 测试并发代码》

这篇文章《使用 testing/synctest 测试并发代码》由 Damien Neil 撰写,发表于 2025 年 2 月 19 日,介绍了 Go 1.24 中引入的实验性包 testing/synctest,旨在简化并发代码的测试。


🤔 并发程序测试的挑战

Go 语言以其内置的并发支持(如 goroutine 和 channel)而闻名,但测试并发程序却常常困难且容易出错。

context.AfterFunc 为例,该函数在上下文被取消后,在新的 goroutine 中调用指定的函数。测试其行为时,我们希望验证:

  1. 在上下文取消之前,函数未被调用。
  2. 在上下文取消之后,函数被调用。

传统的测试方法可能如下:

func TestAfterFunc(t *testing.T) {
    ctx, cancel := context.WithCancel(context.Background())

    calledCh := make(chan struct{})
    context.AfterFunc(ctx, func() {
        close(calledCh)
    })

    // 检查函数未被调用
    select {
    case <-calledCh:
        t.Fatalf("函数在上下文取消前被调用")
    case <-time.After(10 * time.Millisecond):
        // 预期路径
    }

    cancel()

    // 检查函数已被调用
    select {
    case <-calledCh:
        // 预期路径
    case <-time.After(10 * time.Millisecond):
        t.Fatalf("函数在上下文取消后未被调用")
    }
}

这种方法的问题在于:

  • 速度慢:每次测试都需要等待一定时间。
  • 不稳定:在资源受限的环境中(如 CI 系统),可能出现偶发失败。

🧪 引入 testing/synctest 包

为了解决上述问题,Go 1.24 引入了实验性的 testing/synctest 包。该包提供了两个主要函数:

  • Run(f func()):在新的 goroutine 中执行函数 f,并创建一个隔离的“气泡”环境。
  • Wait():等待当前气泡中的所有 goroutine 都处于阻塞状态。

使用 synctest,上述测试可以重写为:

func TestAfterFunc(t *testing.T) {
    synctest.Run(func() {
        ctx, cancel := context.WithCancel(context.Background())

        called := false
        context.AfterFunc(ctx, func() {
            called = true
        })

        synctest.Wait()
        if called {
            t.Fatalf("函数在上下文取消前被调用")
        }

        cancel()

        synctest.Wait()
        if !called {
            t.Fatalf("函数在上下文取消后未被调用")
        }
    })
}

这种方法的优点包括:

  • 速度快:无需实际等待时间。
  • 稳定性高:避免了由于系统负载导致的测试不确定性。
  • 无需修改被测试代码:测试逻辑与业务逻辑解耦。

⏱️ 虚拟时间的优势

synctest 使用虚拟时间来控制测试中的时间相关操作。在气泡环境中,time.Sleep 等函数不会实际等待,而是模拟时间的流逝。这使得测试更加高效和可预测。


🌐 测试网络代码

synctest 也适用于测试网络相关的并发代码,例如验证 net/http 包在处理 100 Continue 响应时的行为。通过模拟时间和控制 goroutine 的调度,可以更精确地测试网络协议的实现。


🚧 实验状态

testing/synctest 是 Go 1.24 中引入的实验性包,默认未启用。要使用它,需要在编译时设置环境变量:(Go)

GOEXPERIMENT=synctest

该包的 API 可能在未来版本中发生变化,建议仅在测试环境中使用。


📚 结语

testing/synctest 提供了一种新的方式来测试并发代码,解决了传统方法中速度慢、不稳定的问题。通过引入隔离的气泡环境和虚拟时间,测试变得更加高效和可靠。虽然目前仍处于实验阶段,但它为 Go 的并发测试提供了有力的工具。

<think>我们正在讨论如何在Simulink模型中同步执行MATLAB代码。用户希望代码与仿真步长同步运行。 解决方案:使用"MATLAB Function"模块或"S-Function"(特别是Level-2 MATLAB S-Function)来编写同步执行的代码。 步骤: 1. MATLAB Function模块:适用于中等复杂度的算法,支持代码生成。 - 在Simulink库浏览器中找到"User-Defined Functions" -> "MATLAB Function" - 将模块拖到模型中,双击打开MATLAB编辑器,编写函数 - 注意:该模块在每个仿真步长(由模型求解器步长决定)都会调用一次,因此是同步的。 2. Level-2 MATLAB S-Function:适用于更复杂的、需要更多控制(如状态管理)的情况。 - 创建S-Function模板:在MATLAB命令窗口输入 `edit sfuntmpl` 查看模板(或使用向导) - 主要需要实现Outputs方法(计算输出)和Update方法(更新离散状态) - 然后在Simulink中使用"S-Function"模块,指定M文件。 注意事项: - 确保代码的执行时间不超过仿真步长,否则会导致实时性问题(在实时仿真中)。 - 如果代码需要访问之前的时间步数据,可以使用离散状态或延迟模块。 参考引用: [^1]: 在Simulink中,使用MATLAB Function模块可以方便地将MATLAB代码集成到模型中,并且该模块会在每个时间步被调用[^1]。 [^2]: Level-2 MATLAB S-Function提供了更多的灵活性,允许用户控制每个时间步的行为,包括初始化、输出计算、状态更新等[^2]。 示例:使用MATLAB Function模块 假设我们有一个简单的需求:在每个时间步计算输入信号的平方。 步骤: 1. 拖入MATLAB Function模块。 2. 双击打开,编写函数: function y = fcn(u) y = u^2; end 3. 连接输入输出,运行仿真。 如果需要更复杂的处理(如状态记忆),可以在MATLAB Function模块中使用persistent变量(相当于离散状态)或使用S-Function。 关于S-Function的简单示例(Level-2): - 创建一个名为'square_sync.m'的S-Function文件。 - 主要方法: function setup(block) block.NumInputPorts = 1; block.NumOutputPorts = 1; block.SampleTimes = [-1 0]; % 继承采样时间(即与模型同步) block.SetPreCompInpPortInfoToDynamic; block.SetPreCompOutPortInfoToDynamic; block.RegBlockMethod('Outputs', @Outputs); end function Outputs(block) u = block.InputPort(1).Data; block.OutputPort(1).Data = u^2; end - 在Simulink中,拖入S-Function模块,指定S-function name为'square_sync'。 注意:S-Function的编写相对复杂,但功能更强大。 另外,如果用户有现有的MATLAB脚本函数,并且希望每个时间步调用,需要将函数封装到上述模块中。 引用[3]中提到To Workspace模块用于导出数据,但这里我们关注的是同步执行代码,所以不直接相关。 因此,推荐使用MATLAB Function模块,因为它更简单。 如果用户需要调用外部MATLAB脚本,可以在MATLAB Function模块中使用`feval`或直接调用(需注意路径),但要注意效率。 总结:对于同步执行,MATLAB Function模块是最直接的方法。 相关问题: 1. 如何在MATLAB Function模块中保存状态(例如实现积分器)? 2. 使用S-Function和MATLAB Function模块在性能上有何差异? 3. 如何确保MATLAB代码在实时仿真中满足时间要求?</think>### 如何在 Simulink 中同步执行 MATLAB 代码 在 Simulink 模型中实现 **同步执行 MATLAB 代码** 的核心方法是使用以下模块,确保代码在每个仿真步长中被调用: #### 1. **MATLAB Function 模块(推荐)** - **原理**:该模块在**每个仿真时间步**自动调用封装的 MATLAB 代码,与模型求解器保持严格同步。 - **操作步骤**: 1. 在 Simulink 库浏览器中找到 `User-Defined Functions > MATLAB Function` 2. 拖入模型并双击打开编辑器 3. 编写需同步执行的代码(示例): ```matlab function y = processInput(u) % 同步处理输入信号 persistent state if isempty(state) state = 0; end state = state + u; % 累加器示例 y = state * 0.5; % 同步计算输出 ``` 4. 连接输入/输出信号线 - **关键设置**: - 模块采样时间设为 `-1`(继承求解器步长) - 启用 **"Allow direct feedthrough"** 确保实时响应输入变化 #### 2. **Level-2 MATLAB S-Function** - **适用场景**:需要更精细控制状态更新/初始化时 - **同步机制**: - 在 `Outputs` 方法中实现核心逻辑 - 在 `Update` 方法中处理状态更新 - **示例结构**: ```matlab function sys = Level2Sfunc(block) setup(block); function setup(block) block.SampleTimes = [Ts 0]; % Ts=仿真步长 function Outputs(block) u = block.InputPort(1).Data; y = sin(u) + rand(); % 同步计算 block.OutputPort(1).Data = y; ``` #### 3. **Interpreted MATLAB Function 模块** - **特点**:适合快速原型设计(无需代码生成) - **同步性**:通过模块采样时间设置与模型同步 - **配置**: ```matlab function y = fcn(u) % 直接编写MATLAB代码 y = myCustomFunction(u); ``` --- ### ⚠️ 关键注意事项 1. **采样时间设置**: - 必须设置为 **与模型求解器步长一致**(例如 `0.01s`) - 错误设置会导致异步执行或数据丢失 2. **状态管理**: - 使用 `persistent` 变量(MATLAB Function)或 `DWork` 向量(S-Function)保存跨步长状态 - 避免全局变量(破坏可重入性) 3. **性能优化**: - 复杂计算建议预编译为 MEX 文件 - 通过 `coder.extrinsic` 声明非代码生成兼容函数 4. **数据交互**: - 输入/输出端口需明确定义数据类型 - 使用 `To Workspace` 模块导出数据时,注意**数据抽取设置**[^3] --- ### 验证同步性的测试方法 1. 在 MATLAB 代码中添加时间戳记录: ```matlab function y = syncTest(u) persistent t_last if isempty(t_last) t_last = 0; end t_now = tout(end); % 引用仿真时间[^2] assert(t_now > t_last); % 验证步进 t_last = t_now; ``` 2. 使用 **Scope 模块** 监控输出连续性 3. 检查工作空间变量导出设置(避免数据抽取导致失真)[^3] > **典型应用场景**:实时控制系统、传感器信号处理、自定义数值算法集成等需要严格时间同步的仿真任务[^1]。 --- ### 相关问题 1. 如何在 MATLAB Function 模块中实现状态记忆(如积分器)? 2. Simulink 实时仿真中如何确保 MATLAB 代码的执行时间不超过步长限制? 3. 使用 S-Function 和 MATLAB Function 模块在性能上有何差异? 4. 如何调试 Simulink 中集成的 MATLAB 代码? [^1]: 通过 `sim` 命令控制仿真可实现代码与模型的深度集成。 [^2]: `To Workspace` 模块的数据抽取设置会影响输出数据的完整性。 [^3]: 同步执行时需禁用数据抽取(`Decimation=1`)以避免采样失真。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值