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

Ubuntu 24.04(WSL2环境)下std::chrono now()存在严重时钟漂移

WSL2 Ubuntu 24.04中std::chrono::system_clock::now()触发时钟异常向前跳转的问题

问题概述

我有一套使用多年的实时时钟模拟算法,通过循环调用std::chrono::system_clock::now()等待到预期时间点后执行下一帧,在非实时环境中表现稳定。但在Windows 11 WSL2的Ubuntu 24.04环境中出现异常:now()的返回值偶尔会在连续调用间大幅向前跳转(单次跳转可达25秒),但实际程序运行耗时远小于跳转时长,且测试证实该调用会主动修改WSL系统时钟。

该问题在以下场景中均复现:

  • 编译器:g++、clang++
  • C标准:C17、C++20
    但在原生Ubuntu 24.04(旧硬件)、WSL2 Ubuntu 22.04(同硬件)环境中运行完全正常。

可复现代码

C++测试代码

#include <iostream>
#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>

void print(std::chrono::duration<long double> elapsed_seconds, 
    std::chrono::_V2::system_clock::time_point, 
    std::chrono::duration<long double> error);

int main() 
{
    std::cout << "commanded_elapsed_time, corresponding_measured_time_points, error" << std::endl; 
    using namespace std::chrono_literals;
    auto start_tp = std::chrono::system_clock::now();
    auto dt = 1.0s;
    auto elapsed = 0.0s;
    auto next_tp = start_tp + elapsed;
    std::chrono::_V2::system_clock::time_point now;
    print(elapsed, start_tp, start_tp - next_tp);
    for (int t = 1; t < 30; t++)
    {
        elapsed = dt * t;
        next_tp = start_tp + elapsed;
        while((now = std::chrono::system_clock::now()) < next_tp) {}
        print(elapsed, now, now - next_tp);
    }
    return 0;
}

void print(
    std::chrono::duration<long double> elapsed_seconds, 
    std::chrono::_V2::system_clock::time_point tp, 
    std::chrono::duration<long double> error)
{
    std::time_t currentTime = std::chrono::system_clock::to_time_t(tp);
    std::tm* localTime = std::localtime(&currentTime);
    std::stringstream ss;
    ss << std::fixed << std::setprecision(3) << std::setfill('0') << std::setw(6) << elapsed_seconds.count() << ", ";
    ss << std::put_time(localTime, "%H:%M:%S");
    auto nowInMicroseconds = std::chrono::time_point_cast<std::chrono::microseconds>(tp);
    auto epoch = nowInMicroseconds.time_since_epoch();
    auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(epoch).count() % 1000000;
    ss << '.' << std::setfill('0') << std::setw(9) << microseconds * 1000 << ", ";
    ss << std::fixed << std::setprecision(9) << error.count();
    std::cout << ss.str() << std::endl;
}

Bash测试脚本

#! /bin/bash
TRUE=0
FALSE=1
using_wsl=$FALSE
echo "kernel name:........" $(uname -s)
echo "kernel release:....." $(uname -r)
echo "kernel version:....." $(uname -v)
echo "machine:............" $(uname -m)
echo "processor:.........." $(uname -p)
echo "hardware platform..." $(uname -i)
echo "operating system...." $(uname -o)
if [ "$(systemd-detect-virt)" = "wsl" ]; then
    echo "running in wsl...... yes"
    using_wsl=$TRUE
    if ! command -v hwclock >/dev/null 2>&1; then
        echo ""
        echo "When running this script within WSL, the 'hwclock'"
        echo "command is used to sync the WSL clock with the"
        echo "host system's clock. 'hwclock' is not found on"
        echo "this system. Install and rerun test."
        exit 1
    fi
else
    echo "running in wsl...... no"
    using_wsl=$FALSE
fi
echo ""
if [ $using_wsl -eq $TRUE ]; then
    echo "WSL time before syncing with host...:" $(date +"%T.%6N")
    echo "syncing WSL clock with host ..."
    sudo hwclock -s
fi
start_time=$(date +"%s.%N")
echo "test system start time..............: $(date -d "@$start_time" +"%T.%6N")"
echo "test starting ..."
echo ""
./zero_drift_dt
end_test_time=$(date +"%s.%N")
echo ""
echo "... test complete"
echo "test system finish time............................: $(date -d "@$end_test_time" +"%T.%6N")"
if [ $using_wsl -eq $TRUE ]; then
    sudo hwclock -s
    echo "WSL time after syncing with host (again)...........:" $(date +"%T.%6N")
fi
final_time=$(date +"%s.%N")
expected_duration=$(awk "BEGIN {print $end_test_time - $start_time}")
actual_duration=$(awk "BEGIN {print $final_time - $start_time}")
error_duration=$(awk "BEGIN {print $end_test_time - $final_time}")
echo "Expected approximate test duration (s).............:" $expected_duration
echo "Measured test duration (s).........................:" $actual_duration
echo "During this test, now() moved clock forward (s)....:" $error_duration

问题系统测试结果

~/Projects/proving_grounds/now_error$ sudo ./test.sh
[sudo] password for blumert:
kernel name:........ Linux
kernel release:..... 5.15.153.1-microsoft-standard-WSL2
kernel version:..... #1 SMP Fri Mar 29 23:14:13 UTC 2024
machine:............ x86_64
processor:.......... x86_64
hardware platform... x86_64
operating system.... GNU/Linux
running in wsl...... yes

WSL time before syncing with host...: 13:39:30.320309
syncing WSL clock with host ...
test system start time..............: 13:39:31.001844
test starting ...

commanded_elapsed_time, corresponding_measured_time_points, error
00.000, 13:39:31.003072000, 0.000000000
01.000, 13:39:32.003072000, 0.000000070
02.000, 13:39:52.464335000, 19.461263572
03.000, 13:39:52.464537000, 18.461465685
04.000, 13:39:52.464569000, 17.461497372
05.000, 13:39:52.464573000, 16.461501714
06.000, 13:39:52.464575000, 15.461503561
07.000, 13:39:52.464577000, 14.461505319
08.000, 13:39:52.464579000, 13.461506932
09.000, 13:39:52.464580000, 12.461508617
10.000, 13:39:52.464582000, 11.461510324
11.000, 13:39:52.464584000, 10.461512480
12.000, 13:39:52.464586000, 9.461514319
13.000, 13:39:52.464588000, 8.461515990
14.000, 13:39:52.464589000, 7.461517739
15.000, 13:39:52.464591000, 6.461519320
16.000, 13:39:52.464593000, 5.461520922
17.000, 13:39:52.464594000, 4.461522511
18.000, 13:39:52.464596000, 3.461524055
19.000, 13:39:52.464597000, 2.461525592
20.000, 13:39:52.464599000, 1.461527167
21.000, 13:39:52.464600000, 0.461528720
22.000, 13:39:57.473118000, 4.470046550
23.000, 13:39:57.473178000, 3.470106269
24.000, 13:39:57.473182000, 2.470110610
25.000, 13:39:57.473184000, 1.470112218
26.000, 13:39:57.473185000, 0.470113725
27.000, 13:40:02.476388000, 4.473316514
28.000, 13:40:02.476452000, 3.473380680
29.000, 13:40:02.476457000, 2.473385528

... test complete
test system finish time............................: 13:40:02.477504
WSL time after syncing with host (again)...........: 13:39:43.002630
Expected approximate test duration (s).............: 31.4757
Measured test duration (s).........................: 12.0018
During this test, now() moved clock forward (s)....: 19.4738

关键异常特征

  • 测试结束后时钟会回退到真实时间,系统闲置后首次同步也会出现该现象
  • 程序设计耗时应略超30秒,但以同步后真实时间计算仅耗时12秒
  • 正常环境中时间点以1秒精确递增,误差仅几十纳秒
  • 首次大幅跳转通常出现在前10秒内,多数在前3秒

问题分析

该问题属于WSL2与Ubuntu 24.04的兼容性bug:

  1. WSL2的系统时钟依赖Windows主机同步,Ubuntu 24.04默认的systemd-timesyncd服务在高频读取时钟(空循环调用now())的场景下,会触发错误的时钟调整逻辑,导致时钟被强制向前跳转。
  2. Ubuntu 22.04未出现问题,说明该bug是Ubuntu 24.04对WSL时钟同步机制的适配问题,或WSL 5.15.153.1内核与Ubuntu 24.04的交互逻辑缺陷。

解决方案

1. 临时缓解:降低时钟读取频率

在等待循环中加入短暂休眠,减少now()的调用次数,避免触发时钟同步bug:

while((now = std::chrono::system_clock::now()) < next_tp) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
}

2. 修复时钟同步服务

替换默认的systemd-timesyncd为更稳定的chrony:

# 停止并禁用默认同步服务
sudo systemctl stop systemd-timesyncd
sudo systemctl disable systemd-timesyncd

# 安装chrony
sudo apt update && sudo apt install chrony -y

# 配置chrony适配WSL
sudo tee -a /etc/chrony/chrony.conf <<EOF
# 允许更大幅度的时钟调整,适配WSL场景
makestep 1.0 3
EOF

# 启动并启用chrony
sudo systemctl start chronyd
sudo systemctl enable chronyd

3. 升级WSL内核

尝试更新WSL到最新版本,微软可能已修复相关时钟bug:

wsl --update

4. 强制同步主机时钟

编辑WSL配置文件/etc/wsl.conf(无则创建):

[boot]
systemd=true
[clock]
synchronize=true

执行以下命令重启WSL生效:

wsl --shutdown

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 06:14:51