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(¤tTime); 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:
- WSL2的系统时钟依赖Windows主机同步,Ubuntu 24.04默认的
systemd-timesyncd服务在高频读取时钟(空循环调用now())的场景下,会触发错误的时钟调整逻辑,导致时钟被强制向前跳转。 - 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
相关产品推荐
相关产品推荐

