首页 > 其他分享 >第四章-线程间的同步操作 4-3 有时间限制的等待

第四章-线程间的同步操作 4-3 有时间限制的等待

时间:2022-11-21 22:48:52浏览次数:40  
标签:std chrono clock 同步操作 线程 时间 now 第四章 时钟

Waiting with a time limit


1. Clocks 时钟

前面介绍的阻塞调用会将线程挂起一段(不确定的)时间,直到相应的事件发生。但在某些情况下,有时可能想要限制等待的时间。

这里有两种指定超时方式:一种是“时间段”(例如,30毫秒),另一种是“时间点”(例如,世界标准时间[UTC]17:30:15.045987023,2011年11月30日)。大多数等待函数都提供了处理这两种超时形式的变体。处理持续时间的变体以_for作为后缀,处理时间点的变体以_until作为后缀。例如,std::condition_variablewait_for()成员函数wait_until()成员函数。

就 c++ 标准库而言,时钟是时间信息的来源。具体来说,时钟是一个提供四个不同信息片段的类:

  • 当前时间 now
  • 用于表示从时钟获得的时间的值的类型:时间类型
  • 时钟节拍
  • 稳定时钟

当前时间可以通过静态成员函数now()从获取。例如,std::chrono::system_clock::now()会返回系统的当前时间,返回类型是 system_clock 中 typedef 的 time_point(一个类)(其他时钟类型也是,比如 steady_clock::now 返回的是 steady_clock::time_point)。

时钟节拍被指定为1/x(x在不同硬件上有不同的值)秒,这是由时间周期所决定——一个时钟一秒有25个节拍,因此一个周期为std::ratio<1, 25>,当一个时钟的时钟节拍每2.5秒一次,周期就可以表示为std::ratio<5, 2>。不能保证在给定的程序运行中观察到的时钟节拍与指定的时钟节拍相匹配。

当时钟节拍均匀分布(无论是否与周期匹配),并且不可修改,这种时钟就称为稳定时钟。is_steady静态数据成员为true时,也表明这个时钟就是稳定的。通常情况下,因为std::chrono::system_clock可调,所以是不稳定的。通常,std::chrono::system_clock不会是稳定的,因为时钟可以调整,即使这种调整是自动进行的。这样的调整可能会导致对now()的调用返回的值早于之前对now()的调用返回的值,这违反了对统一计时率的要求。稳定时钟对于超时计算很重要,所以c++标准库以std::chrono::steady_clock的形式提供了一个。c++标准库提供的std::chrono::system_clock,代表了系统时钟的“实际时间”,并且提供了函数,可将时间点转化为time_t类型的值。std::chrono::high_resolution_clock 可能是标准库中提供的具有最小节拍周期(因此具有最高的精度)的时钟。

2. Durations 时间段

std::chrono::duration<>函数模板能够对时间段进行处理。第一个模板参数是表示的类型(例如int、long或double),第二个是一个分数,指定一个时间段代表的秒数。例如,存储在short中的分钟数是std::chrono::duration<short,std:: ratio<60,1>>,因为一分钟有60秒。另一方面,存储在double中的毫秒计数是std::chrono::duration<double,std::ratio<1,1000>>,因为每毫秒是1/1000秒。标准库在std::chrono命名空间内为时间段变量提供一系列预定义类型:nanoseconds[纳秒] , microseconds[微秒] , milliseconds[毫秒] , seconds[秒] , minutes[分]和hours[时]。它们都使用一个足够大的整数类型来表示所选择的表示形式。

C++14中std::chrono_literals命名空间中有许多预定义的后缀操作符用来表示时长。下面的代码就是使用硬编码的方式赋予变量具体的时长:

using namespace std::chrono_literals;
auto one_day=24h;
auto half_an_hour=30min;
auto max_time_between_messages=30ms;

使用整型字面符时,15ns和std::chrono::nanoseconds(15)就是等价的。不过,当使用浮点字面量时,且未指明表示类型时,数值上会对浮点时长进行适当的缩放。因此,2.5min会被表示为 std::chrono::duration<some-floating-point-type,std::ratio<60,1>> 。如果非常关心所选的浮点类型表示的范围或精度,就需要构造相应的对象来保证表示范围或精度,而不是去苛求字面值来对范围或精度进行表达。

当不要求截断值的情况下(时转换成秒是没问题,但是秒转换成时就不行)时间段的转换是隐式的,显示转换可以由std::chrono::duration_cast<>来完成。

std::chrono::milliseconds ms(54802);
std::chrono::seconds s=
       std::chrono::duration_cast<std::chrono::seconds>(ms);
// 这里的结果就是截断的,而不是进行了舍入,所以s最后的值为54

时间段支持四则运算,所以能够对两个时间段进行加减,或者是对一个时间段乘除一个常数(模板的第一个参数)来获得一个新时间段变量。例如,5*seconds(1)与seconds(5)或minutes(1)-seconds(55)是一样。在时间段中可以通过count()成员函数获得单位时间的数量。例如,std::chrono::milliseconds(1234).count()就是1234。

基于时间段的等待可由std::chrono::duration<>来完成。例如:等待future状态变为就绪需要35毫秒:

std::future<int> f=std::async(some_task);
if(f.wait_for(std::chrono::milliseconds(35))==std::future_status::ready)
  do_something_with(f.get());

wait 函数会返回一个状态,表示是等待超时还是等待的事件发生了。 std::future_status::timeout 表示超时, std::future_status:: ready 表示 future 已经准备好了, std::future_status::deferred 表示 future 的任务被推迟了。基于时间段的等待是通过稳定时钟计算的,因此即使系统时间被调整,也不受影响。当然,系统调度和操作系统时钟的不同精度意味着线程发出调用和从调用返回之间的时间可能比35ms长。

3. Time points 时间点

时钟的时间点由std::chrono::time_ point<>类模板的实例表示,该类模板指定了引用哪个时钟作为第一个模板参数,并指定度量单位(std::chrono::duration<>的特化)作为第二个模板参数。时间点的值是从某个特殊的时间点(一般称作 epoch)开始算起的一个时间长度。典型的 epoch 是 UNIX 的1970年1月1日 00:00,这是一个基本属性,不可以直接查询得到或者也不是由c++标准指定的,虽然不能找出epoch的时间,但是可以通过time_since_epoch() 函数查询给定的time_point 到 epoch 的时间长度 。

可以通过对std::chrono::time_point<>实例进行加/减,来获得一个新的时间点,所以std::chrono::hight_resolution_clock::now() + std::chrono::nanoseconds(500)将得到500纳秒后的时间,这对于计算绝对时间来说非常方便。

auto start=std::chrono::high_resolution_clock::now();
do_something();
auto stop=std::chrono::high_resolution_clock::now();
std::cout<<"do_something() took "
  <<std::chrono::duration<double,std::chrono::seconds>(stop-start).count()
  <<" seconds"<<std::endl;

时间点与 wait 函数的_until变体一起使用。

#include <condition_variable>
#include <mutex>
#include <chrono>

std::condition_variable cv;
bool done;
std::mutex m;

bool wait_loop()
{
  auto const timeout= std::chrono::steady_clock::now()+
      std::chrono::milliseconds(500);
  std::unique_lock<std::mutex> lk(m);
  while(!done)
  {
    if(cv.wait_until(lk,timeout)==std::cv_status::timeout)
      break;
  }
  return done;
}

如果没有传递要等待的谓词,建议使用这种方法来等待有时间限制的条件变量。如果没有传入谓词,则需要在使用条件变量时进行循环,以便处理虚假唤醒。

4. Functions that accept timeouts

处理超时的两个函数:std::this_thread::sleep_for()和std::this_thread::sleep_until()。
std::mutex和std::recursive_mutex都不支持超时,而std::timed_mutex和std::recursive_timed_mutex支持超时。这两种类型也有try_lock_for()和try_lock_until()成员函数,可以在一段时期内尝试获取锁,或在指定时间点前获取互斥锁。
image.png
image.png


参考:
[1] Anthony Williams - C++ Concurrency in Action-Manning Publications (2019).

标签:std,chrono,clock,同步操作,线程,时间,now,第四章,时钟
From: https://www.cnblogs.com/snowflakes-x/p/16913644.html

相关文章