问题概述

系统时间跳变会导致一些系统时间依赖函数的异常工作,一个例子是sem_timedwait。当处于sem_timedwait阻塞等待时,如果此时系统时间跳变到起始时间之前:

1
sudo date -s "2025-06-30 9:40:00" //修改系统时间

那么sem_timedwait会处于持续的阻塞状态,sem_timedwait不会返回,也不会产生任何打印效果,如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <time.h>
#include <semaphore.h>
#include <thread>

using namespace std;

struct timespec ts{};

int main(){
sem_t sem_;
sem_init(&sem_,0,0);
while(true){
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 20;
std::this_thread::sleep_for(std::chrono::seconds(10)); //在此延时内向前改变时间
if(sem_timedwait(&sem_, &ts)){ //没有获取到信号量
cout << "sem_timewait exit!" << endl;
break;
}else{ //获取到信号量
cout << "got semphore" <<endl;
break;
}
cout << "on Loop!" <<endl;
}

cout << "done" <<endl;
return 0;
}

问题解决:sem_trywait + 超时等待实现sem_timedwait

此处的解决方法是通过sem_trywait去检测信号量,通过延时时间作超时等待,可以和sem_timedwait等效:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int mySemWait(sem_t* sem, int64_t waitTime_ms){
const int64_t MaxWaitTime = 10; //每次最大轮询时间为10ms
int64_t wait_pertime = 1; //单次等待时间,从1ms逐次递增

std::chrono::steady_clock::time_point startTime = std::chrono::steady_clock::now();
int64_t duration = 0;

do{
if(sem_trywait(sem) == 0){
return 0;
}
if(errno != EAGAIN){
return -1;
}
int64_t leftTime = waitTime_ms - duration;
std::this_thread::sleep_for(std::chrono::milliseconds(std::min(leftTime, std::min(wait_pertime, MaxWaitTime))));
wait_pertime *= 2;
std::chrono::steady_clock::time_point endTime = std::chrono::steady_clock::now();
duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
}while(duration < waitTime_ms);

return -1;
}

参考链接:

修改系统时间,导致sem_timedwait 一直阻塞的问题解决和分析