【sem_timedwait/std::condition_variable因系统时跳致线程阻塞问题解决方案】
sem_timedwait问题概述
系统时间跳变会导致一些系统时间依赖函数的异常工作,一个例子是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
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
23int 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;
}
std::condition_variable wait_for时跳问题
如果使用系统时钟,一些版本的std::condition_variable向前时跳后使用wait_for/wait_util都会造成线程长久阻塞,如Linux下这样的例子:
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using namespace std;
using std::endl;
using std::cout;
std::condition_variable cv; //条件变量
std::mutex mtx;
int main() {
auto begin = std::chrono::steady_clock::now();
auto beginTime = std::chrono::system_clock::now();
// 转换为 time_t
std::time_t beginTimet = std::chrono::system_clock::to_time_t(beginTime);
std::cout << "BeginPoint: " << std::ctime(&beginTimet);
std::thread t([](){
std::unique_lock<std::mutex> lock(mtx);
cout << "son thread..." << endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
//cv.notify_one(); //不唤醒
});
t.detach();
std::unique_lock<std::mutex> lock(mtx);
cout << "Please change the time within 10s ! " << endl;
cv.wait_for(lock, std::chrono::seconds(10)); //正常等待10s超时即继续,设置向前时跳后会长久阻塞
cout << "done" << endl;
auto end = std::chrono::steady_clock::now();
cout << std::chrono::duration_cast<std::chrono::seconds>(end - begin).count() << endl;
auto endTime = std::chrono::system_clock::now();
std::time_t endTimet = std::chrono::system_clock::to_time_t(endTime);
std::cout << "EndPoint: " << std::ctime(&endTimet);
return 0;
}
解决这样的问题,最保守的方法是引入boost库的条件变量,因为发现早期gcc(gcc
8/9)等steady clock极其不完善,查看其wait_for实现,发现其通过宏去兼容系统时钟和稳定时钟,类似:
1
2
3
4
5
6
7
8
9
10
11template<typename _Lock, typename _Rep, typename _Period>
cv_status
wait_for(_Lock& __lock, const chrono::duration<_Rep, _Period>& __rtime)
{ return wait_until(__lock, __clock_t::now() + __rtime); }
template<typename _Lock, typename _Rep,
typename _Period, typename _Predicate>
bool
wait_for(_Lock& __lock,
const chrono::duration<_Rep, _Period>& __rtime, _Predicate __p)
{ return wait_until(__lock, __clock_t::now() + __rtime, std::move(__p)); }__clock_t::now()存在两种实现:
1
2
3
4
5
using __clock_t = steady_clock;
using __clock_t = system_clock;
但有可能尽管定义了相关的宏、glibc库等在2.31版本以上,仍然没有成功避免阻塞问题,特别是存在多个gcc环境时,需要使用上述demo二次验证。
boost库是最干净利落的,其果断抛弃了系统时钟的写法: 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using namespace std;
using std::endl;
using std::cout;
boost::condition_variable cv; //条件变量
boost::mutex mtx;
int main() {
auto begin = std::chrono::steady_clock::now();
auto beginTime = std::chrono::system_clock::now();
// 转换为 time_t
std::time_t beginTimet = std::chrono::system_clock::to_time_t(beginTime);
std::cout << "111BeginPoint: " << std::ctime(&beginTimet);
std::thread t([](){
boost::unique_lock<boost::mutex> lock(mtx);
cout << "222son thread..." << endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
//cv.notify_one(); //不唤醒
});
t.detach();
boost::unique_lock<boost::mutex> lock(mtx);
cout << "111111Please change the time within 10s ! " << endl;
cv.wait_for(lock, boost::chrono::seconds(10));
//cv.wait_until(lock, std::chrono::steady_clock::now() + std::chrono::seconds(10));
cout << "done" << endl;
auto end = std::chrono::steady_clock::now();
cout << std::chrono::duration_cast<std::chrono::seconds>(end - begin).count() << endl;
auto endTime = std::chrono::system_clock::now();
std::time_t endTimet = std::chrono::system_clock::to_time_t(endTime);
std::cout << "111EndPoint: " << std::ctime(&endTimet);
return 0;
}
参考链接:

