admin管理员组

文章数量:1599539

偶然一次测试的时候发现时间前调时会发现condition_variable.wait_for锁住

目标环境:CentOS7 + gcc9.3

调查发现:

std::condition_variable内部使用的是system_clock,会随着系统时间的变化而变化。

编译使用的stl版本为9,默认的版本是4.8.5,对应的实现如下

stl版本4.8.5
class condition_variable
{
  typedef chrono::system_clock        __clock_t;
  ......
}

template<typename _Rep, typename _Period>
cv_status
wait_for(unique_lock<mutex>& __lock,
            const chrono::duration<_Rep, _Period>& __rtime)
{ return wait_until(__lock, __clock_t::now() + __rtime); }
stl版本9
class condition_variable
{
  typedef chrono::system_clock        __clock_t;
  typedef chrono::steady_clock        __steady_clock_t;
  ......
}

template<typename _Rep, typename _Period, typename _Predicate>
bool wait_for(unique_lock<mutex>& __lock,
                 const chrono::duration<_Rep, _Period>& __rtime,
                 _Predicate __p)
{
  using __dur = typename __steady_clock_t::duration;
  auto __reltime = chrono::duration_cast<__dur>(__rtime);
  if (__reltime < __rtime)
     ++__reltime;
  return wait_until(__lock, __steady_clock_t::now() + __reltime,
                    std::move(__p));
}


template<typename _Dur>
cv_status __wait_until_impl(unique_lock<mutex>& __lock,
                      const chrono::time_point<__clock_t, _Dur>& __atime)
{
   auto __s = chrono::time_point_cast<chrono::seconds>(__atime);
   auto __ns = chrono::duration_cast<chrono::nanoseconds>(__atime - __s);

   ......

   return (__clock_t::now() < __atime
              ? cv_status::no_timeout : cv_status::timeout);
}

   这两个版本中使用的都是system_clock作为计时器,因此时间前调时会出现等待时间超过预期值的问题。

   后来又在Windows上试了下,发现VS2019并不存在该问题。

template <class _Rep, class _Period, class _Predicate>
bool wait_for(unique_lock<mutex>& _Lck, const chrono::duration<_Rep, _Period>&  _Rel_time, _Predicate _Pred) {
     // wait for signal with timeout and check predicate
     return _Wait_until1(_Lck, chrono::steady_clock::now() + _Rel_time, _Pred);
}

解决方法:

(一)使用boost的condition_variable

boost在1.60修复了该问题

使用steady_lock,需要在项目中增加宏:BOOST_CHRONO_POSIX_API

备注:增加该宏后会有个编译器的告警提示。可以考虑注释掉该告警boost/chrono/config.hpp

(二)自己实现condition_variable

网络的上的参考代码

参考写法:

#include <pthread.h>

// Declare the necessary variablespthread_mutex_t m_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_condattr_t m_attr;
pthread_cond_t m_cond;

// Set clock to monotonicpthread_condattr_init(&m_attr);
pthread_condattr_setclock(&m_attr, CLOCK_MONOTONIC);
pthread_cond_init(&m_cond, &m_attr);

// Wait on datastruct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
ts.tv_sec += timout_in_seconds;
pthread_mutex_lock(&m_mutex);
int rc = pthread_cond_timedwait(&m_cond, &m_mutex, &ts);
if (rc != ETIMEDOUT)
; // do things with dataelse
; // error: timeout// ...pthread_mutex_unlock(&m_mutex); // have to do it manually to unlock// ...

// Notify the data is readypthread_cond_broadcast(&m_cond);

(三)升级gcc和glibc

        gcc10,提供了稳定递增时间戳的方案

但是其实现依托于pthread_cond_clockwait,目前的CentOS默认的glibc2.17版本不支持

glibc-2.30才支持pthread_cond_clockwait,如图

 

 使用时项目中需要定义宏:_GLIBCXX_USE_PTHREAD_COND_CLOCKWAIT

参考链接:

#11377 (Boost condition variable always waits for system clock deadline) – Boost C++ Libraries

make condition_variable_any steady when BOOST_THREAD_HAS_CONDATTR_SET… · boostorg/thread@8f5de1d · GitHub

c++ - How do I deal with the system clock changing while waiting on a std::condition_variable? - Stack Overflow

本文标签: 线程隐患conditionvariable