std::condition_variable wait_until 令人惊讶的行为
std::condition_variable wait_until surprising behaviour
使用 VS2013 构建,将 time_point::max()
指定为条件变量的 wait_until
会导致立即超时。
这似乎不符合直觉 - 我会天真地期望 time_point::max()
无限期地等待(或者至少 非常 很长时间)。谁能确认这是否已记录在案、预期行为或特定于 MSVC 的行为?
下面的示例程序;请注意将 time_point::max()
替换为 now + std::chrono::hours(1)
会给出预期的行为(wait_for
在通知 cv 后退出,没有超时)
#include <condition_variable>
#include <mutex>
#include <chrono>
#include <future>
#include <functional>
void fire_cv( std::mutex *mx, std::condition_variable *cv )
{
std::unique_lock<std::mutex> lock(*mx);
printf("firing cv\n");
cv->notify_one();
}
int main(int argc, char *argv[])
{
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
std::condition_variable test_cv;
std::mutex test_mutex;
std::future<void> s;
{
std::unique_lock<std::mutex> lock(test_mutex);
s = std::async(std::launch::async, std::bind(fire_cv, &test_mutex, &test_cv));
printf("blocking on cv\n");
std::cv_status result = test_cv.wait_until( lock, std::chrono::steady_clock::time_point::max() );
//std::cv_status result = test_cv.wait_until( lock, now + std::chrono::hours(1) ); // <--- this works as expected!
printf("%s\n", (result==std::cv_status::timeout) ? "timeout" : "no timeout");
}
s.wait();
return 0;
}
我调试了MSCV 2015的实现,wait_until
内部调用了wait_for
,实现是这样的:
template<class _Rep,
class _Period>
_Cv_status wait_for(
unique_lock<mutex>& _Lck,
const chrono::duration<_Rep, _Period>& _Rel_time)
{ // wait for duration
_STDEXT threads::xtime _Tgt = _To_xtime(_Rel_time); // Bug!
return (wait_until(_Lck, &_Tgt));
}
这里的错误是_To_xtime
溢出,导致未定义的行为,结果是负面time_point
:
template<class _Rep,
class _Period> inline
xtime _To_xtime(const chrono::duration<_Rep, _Period>& _Rel_time)
{ // convert duration to xtime
xtime _Xt;
if (_Rel_time <= chrono::duration<_Rep, _Period>::zero())
{ // negative or zero relative time, return zero
_Xt.sec = 0;
_Xt.nsec = 0;
}
else
{ // positive relative time, convert
chrono::nanoseconds _T0 =
chrono::system_clock::now().time_since_epoch();
_T0 += chrono::duration_cast<chrono::nanoseconds>(_Rel_time); //Overflow!
_Xt.sec = chrono::duration_cast<chrono::seconds>(_T0).count();
_T0 -= chrono::seconds(_Xt.sec);
_Xt.nsec = (long)_T0.count();
}
return (_Xt);
}
默认情况下,std::chrono::nanoseconds
将其值存储在 long long
中,因此在其定义之后,_T0
的值为 1'471'618'263'082'939'000
(这显然发生了变化)。添加 _Rel_time
(9'223'244'955'544'505'510
) 肯定会导致有符号溢出。
我们已经通过了每一个可能的负值 time_point
,所以超时了。
使用 VS2013 构建,将 time_point::max()
指定为条件变量的 wait_until
会导致立即超时。
这似乎不符合直觉 - 我会天真地期望 time_point::max()
无限期地等待(或者至少 非常 很长时间)。谁能确认这是否已记录在案、预期行为或特定于 MSVC 的行为?
下面的示例程序;请注意将 time_point::max()
替换为 now + std::chrono::hours(1)
会给出预期的行为(wait_for
在通知 cv 后退出,没有超时)
#include <condition_variable>
#include <mutex>
#include <chrono>
#include <future>
#include <functional>
void fire_cv( std::mutex *mx, std::condition_variable *cv )
{
std::unique_lock<std::mutex> lock(*mx);
printf("firing cv\n");
cv->notify_one();
}
int main(int argc, char *argv[])
{
std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now();
std::condition_variable test_cv;
std::mutex test_mutex;
std::future<void> s;
{
std::unique_lock<std::mutex> lock(test_mutex);
s = std::async(std::launch::async, std::bind(fire_cv, &test_mutex, &test_cv));
printf("blocking on cv\n");
std::cv_status result = test_cv.wait_until( lock, std::chrono::steady_clock::time_point::max() );
//std::cv_status result = test_cv.wait_until( lock, now + std::chrono::hours(1) ); // <--- this works as expected!
printf("%s\n", (result==std::cv_status::timeout) ? "timeout" : "no timeout");
}
s.wait();
return 0;
}
我调试了MSCV 2015的实现,wait_until
内部调用了wait_for
,实现是这样的:
template<class _Rep,
class _Period>
_Cv_status wait_for(
unique_lock<mutex>& _Lck,
const chrono::duration<_Rep, _Period>& _Rel_time)
{ // wait for duration
_STDEXT threads::xtime _Tgt = _To_xtime(_Rel_time); // Bug!
return (wait_until(_Lck, &_Tgt));
}
这里的错误是_To_xtime
溢出,导致未定义的行为,结果是负面time_point
:
template<class _Rep,
class _Period> inline
xtime _To_xtime(const chrono::duration<_Rep, _Period>& _Rel_time)
{ // convert duration to xtime
xtime _Xt;
if (_Rel_time <= chrono::duration<_Rep, _Period>::zero())
{ // negative or zero relative time, return zero
_Xt.sec = 0;
_Xt.nsec = 0;
}
else
{ // positive relative time, convert
chrono::nanoseconds _T0 =
chrono::system_clock::now().time_since_epoch();
_T0 += chrono::duration_cast<chrono::nanoseconds>(_Rel_time); //Overflow!
_Xt.sec = chrono::duration_cast<chrono::seconds>(_T0).count();
_T0 -= chrono::seconds(_Xt.sec);
_Xt.nsec = (long)_T0.count();
}
return (_Xt);
}
默认情况下,std::chrono::nanoseconds
将其值存储在 long long
中,因此在其定义之后,_T0
的值为 1'471'618'263'082'939'000
(这显然发生了变化)。添加 _Rel_time
(9'223'244'955'544'505'510
) 肯定会导致有符号溢出。
我们已经通过了每一个可能的负值 time_point
,所以超时了。