• 发生时间:2023-08-11

  • 问题描述:

    • 为支持项目算法测试,我们开发了 bmcan 组件,用于跟 BUSMUSTER 进行 CAN FD 通信。
    • 调试过程中,发现经常无法通过 bmcan 收到特定数据的算法告警报文。
  • 问题类别:软件开发

  • 原因分析:

    • 查看 bmcan 的调试日志,发现可以收到大量目标 CAN ID 的报文,只是没收到算法告警报文。
    • 从 BUSMUSTER 自带的官方上位机界面可以看到,设备会周期性发送大概10种 CAN ID 的报文,发送周期在 1~10 ms之间。
    • 初步推测原因是 bmcan 的收包速度跟不上设备的发包速度。
    • 在 bmcan 的代码中增加打印日志,最终在 reader.pyread_repeatly 函数中发现异常:
      • 理论上,此函数每调用底层 SDK 成功接收一次报文后,会 sleep 1 毫秒,然后重新接收报文。
      • 实际上,此函数每次 sleep 的时间大约是 14 毫秒,远超期望的 1 毫秒!这导致收包速度明显跟不上发包速度。
    • 经测试,Windows 系统 Python 3.8.3 里面,time.sleep 的最小精度只有14毫秒左右。(详见下文)
    • 经同事提醒,在网上找到一个使用 C/C++ 提高 Windows 系统 sleep 精度的解决方案,但毕竟是跨语言,比较麻烦。
    • 后来在 cpython 的 Github Issue 页面 找到一个相关问题的讨论,该 issue 已经被 Closed,我意识到可能新版本的 Python 已经解决此问题。
    • 因此,我将 Python 版本更新到 3.11.4,果然 time.sleep 的最小精度变为1毫秒左右。
  • 解决方案:将 Python 版本更新到 3.11.4.

  • 实施结果:time.sleep 函数可以正常工作,修改后 bmcan 能够正常收到算法告警报文。

  • 经验总结:

    • Python 3.8.3 或更早的版本的 time.sleep 的最小时间精度为 14 毫秒左右,Python 3.11.4 的 time.sleep 的最小时间精度为 1 毫秒左右。
    • 如果发现旧版本的 Python 存在一些问题或局限,可以尝试升级 Python 到最新的稳定版本,也许可以解决问题。

Windows 系统 不同 Python 版本的 time.sleep 函数的行为差异

测试程序

import time

def test_sleep(seconds: float):
    start = time.time()
    time.sleep(seconds)
    end = time.time()
    print(f"计划睡眠 {seconds} s,实际睡眠 {end - start} s")

test_sleep(0.1)
test_sleep(0.01)
test_sleep(0.001)

Python 3.8.3 的运行结果

计划睡眠 0.1 s,实际睡眠 0.11043739318847656 s
计划睡眠 0.01 s,实际睡眠 0.01606273651123047 s
计划睡眠 0.001 s,实际睡眠 0.014087438583374023 s

Python 3.11.4 的运行结果

计划睡眠 0.1 s,实际睡眠 0.10086703300476074 s
计划睡眠 0.01 s,实际睡眠 0.010546207427978516 s  
计划睡眠 0.001 s,实际睡眠 0.0012774467468261719 s

结论

对比 Python 3.8.3 和 Python 3.11.4 的运行结果,可以看出后者的睡眠时间精度明显高于前者。 特别是睡眠1毫秒的时候,后者的误差为 20%,前者的误差为 700% !