C++ Clock和Timer(chrono库)

原文链接: Clock和Timer(chrono库)

一、Clock和Timer

  • 在过去,C和POSIX提供的系统时间接口,允许从秒转换至毫秒,再至微秒,最终至纳秒,问题是每次转换就需要一个新接口
  • 基于这个原因,C++11开始提供一个精度中立的程序库,称为chrono程序库,被定义于<chrono>中

二、Chrono程序库概述

  • Chrono程序库的设计,是希望能够处理“timer和clock在不同系统中可能不同”的事实,同时也是为了强化实践精准度
  • 为了避免像POSIX的time程序库那样每十年就引入一个新的时间类型,C++标准库的目标是提供一个精度中立概念,把duration(时间段)和timepoint(时间点)从特定clock(时钟)区分开来。最终结果就是chrono程序库核心由以下类型或概念组成:
    • duration(时间段):值得是在某时间单位上的一个明确的tick(片刻数)。例如,“3分钟”就是指一个时间段
    • timepoint(时间点):一个duration和一个epoch(起始点)的组合。例如,“2000年新年夜”就是一个时间点

  • Chrono程序库定义在<chrono>头文件中,命名空间为namespace std::chrono

三、Duration(时间段)

duration的基本格式

  • Duration是一个数值和一个分数的组合(其中的分数由ratio<>描述)
    • 模板参数1:是一个数值,用来表示时间段所经历的滴答(tick)数
    • 模板参数2:用来表示时间段每个滴答的单位(秒、毫秒等等)。可以省略,省略时默认以秒为单位
  • 例如:
  • twentySeconds:代表20秒,20*1秒=20秒(以秒为单位)
  • twentySeconds:代表0.5分钟,0.5*60秒=0.5分钟(以分钟为单位(60/1))
  • twentySeconds:代表1毫秒,1*0.001秒=1毫秒(以毫秒为单位(1/1000))
std::chrono::duration<int>                       twentySeconds(20);
std::chrono::duration<double, std::ratio<60>>    halfAMinute(0.5);
std::chrono::duration<long, std::ratio<1, 1000>> oneMillisecond(1);

chrono自带的duration类型定义

  • 在chrono中自定义了下面的类型,可以直接使用

  • 例如,下面可以轻松的指定一些时间段
std::chrono::seconds      twentySeconds(20); //20秒
std::chrono::hours        aDay(24);          //24小时
std::chrono::milliseconds oneMillisecond(1); //1毫秒

Duration的算术运算

下图列出了duration可以进行的算术运算。例如:

  • 你可以计算两个duration的和、差、积和商
  • 你可以加减tick,或加减其他duration
  • 你可以比较两个duration的大小

运算所涉及的两个duration的单位类型可以不同:

  • 标准库的common_type<>为duration提供了一个重载版本
  • 因此运算所得的那个duration,其单位将是两个操作数的单位的最大公约数演示案例
std::chrono::seconds      d1(42); //42秒
std::chrono::milliseconds d2(10); //10毫秒
 
d1 - d2; //返回41990个毫秒为单位的一个duration,42000-10=41990
d1 < d2; //返回false
std::chrono::duration<int, std::ratio<1, 3>> d1(1); //1/3秒
std::chrono::duration<int, std::ratio<1, 5>> d2(1); //1/5秒
 
d1 + d2; //返回8/15秒,1/3+1/5=8/15
d1 < d2; //返回false
  • 你也可以将duration转换为不同的单位,只要彼此之间存在隐式转换即可。因此,你可以将小时转换为秒,但是反向不可以(详情还可以见下面的duration_cast<>()的介绍)。例如:
std::chrono::seconds twentySeconds(20); //20秒
std::chrono::hours   aDay(24);          //24小时
 
std::chrono::milliseconds ms;           //0毫秒
ms += twentySeconds + aDay;             //86400000毫秒
--ms;                                   //86399999毫秒
ms *= 2;                                //172839998毫秒
 
std::cout << ms.count() << " ms" << std::endl;
std::cout << std::chrono::nanoseconds(ms).count() << " ns" << std::endl;

Duration的其他操作

  • 下面列出了duration支持的其它操作

  • duration的默认构造函数会以默认方式初始化其数值,因此基础类型的初值是不明确的
  • duration提供三个静态函数:zero()产出一个0秒duration,min()和max()分别产出一个duration所能拥有的最小值和最大值
  • 例如,下面为duration对象添加一个operator<<版本
template<typename V,typename R>
std::ostream& operator<<(std::ostream& s, const std::chrono::duration<V, R>& d)
{
    s << "[" << d.count() << " of " << R::num << "/" << R::den << "]";
    return s;
}
 
int main()
{
    std::chrono::milliseconds d(42);
    std::cout << d << std::endl;
}

duration_cast<>

  • 在上面我们介绍过duration的类型转换,可以将一个低精度的单位类型转换为一个高精度的单位类型(例如,将分钟转换为秒,将秒转换为微秒),但是不能将一个高精度的单位类型转换为一个低精度的单位类型(例如,将微秒转换为秒,将秒转换为分钟等。因为这可能会造成数据的丢失,例如将42010毫秒转换为秒,结果是42,那么原本的10毫秒就丢失了)
  • 如果想要将高精度的单位类型转换为一个低精度的单位类型,那么可以使用duration_cast<>进行强制转换
  • 例如:
std::chrono::seconds sec(55);
 
//错误的,默认不能将秒转换为分钟
std::chrono::minutes m1 = sec;
 
//正确的,可以使用duration_cast,将秒转换为分钟
std::chrono::minutes m2 = std::chrono::duration_cast<std::chrono::minutes>(sec);
  • 将浮点数类型的duration转换为整数类型的duration也需要使用duration_cast<>。例如:
std::chrono::duration<double, std::ratio<60>> halfMin(0.5);
 
//错误,halfMin的tick为double类型,s1的tick默认为int类型
std::chrono::seconds s1 = halfMin;
 
//正确,使用duration_cast强制转换
std::chrono::seconds s2 = std::chrono::duration_cast<std::chrono::seconds>(halfMin)
  • 演示案例:
    • 下面代码把duration切割为不同单元,例如让一个以毫秒为单位的duration切割为相对应的小时、分钟、秒钟、毫秒
    • 在下面我们将ms转换为小时hh,实际数值会被截断而四舍五入
    • 幸好有%运算符,我们可以把一个duration当做其第二实参,于是写下ms%std::chrono::hours(1)轻松处理剩余的毫秒,那么毫秒又被转换为分钟
std::chrono::milliseconds ms(7255042);
 
std::chrono::hours hh = std::chrono::duration_cast<std::chrono::hours>(ms);
std::chrono::minutes mm = std::chrono::duration_cast<std::chrono::minutes>(ms%std::chrono::hours(1));
std::chrono::seconds ss = std::chrono::duration_cast<std::chrono::seconds>(ms%std::chrono::minutes(1));
std::chrono::milliseconds msec = std::chrono::duration_cast<std::chrono::milliseconds>(ms%std::chrono::seconds(1));
 
std::cout << "raw: " << hh << "::" << mm << "::" << ss << "::" << msec << std::endl;
std::cout << "     " << setfill('0') << setw(2) << hh.count() << "::"
                                     << setw(2) << mm.count() << "::"
                                     << setw(2) << ss.count() << "::"
                                     << setw(3) << msec.count() << std::endl;

四、Clock(时钟)

  • Clock(时钟):
    • 定义一个epoch(起始点)和一个tick周期
    • 例如,某个clock也许定义tick周期为毫秒,起始点是UNIX epoch
    • 此外,clock还提供一个类型给“与此clock关联”的任何timepoint使用
    • Clock提供的函数now()可以产出一个代表“现在时刻”的timepoint对象

Clock提供的操作

  • 下图列出了clock提供的类型定义和static成员

三个clock

  • 标准库提供了三个clock:
    • system_clock:它所表现的timepoint将关联至现行系统的即时时钟
      • 这个clock提供便捷函数to_time_t()和from_time_t(),允许我们在timepoint和“C的系统时间类型”timet之间转换,这意味着你可以转换至日历时间
    • strady_clock:它保证绝不会被调用,因此当实际时间流逝,其timepoint值绝不会减少,而且这些timepoint相对于真实时间都有稳定的前进速率
    • high_resolution_clock:它所表现的是当前系统中带有最短tick周期的clock
  • 这三个clock都支持上面Clock提供的操作

三个clock的精度问题

  • 标准库并不强制规定上述clock的精准度、epoch,“最小和最大timepoint的范围”。举个例子,你的system clock也许提供的是UNIX epoch(1970年1月1日),如果你还需要一个特定的epoch,或你关注的timepoint并非被你的clock涵盖,你就必须使用各种便捷函数查清楚
  • 例如,下面的函数打印某个clock的各种属性:
template<typename C>
void printClockData()
{
    std::cout << "- precision: ";
    typedef typename C::period P;
    if (std::ratio_less_equal<P, milli>::value){
        typedef typename std::ratio_multiply<P, kilo>::type TT;
        std::cout << fixed << double(TT::num) / TT::den << " milliseconds" << std::endl;
    }
    else {
        std::cout << fixed << double(P::num) / P::den << " seconds" << std::endl;
    }
    std::cout << "- si_steady: " << boolalpha << C::is_steady << std::endl;
}
  • 我们可以针对各种clock调用这个函数,例如:
int main()
{
    std::cout << "system_clock: " << std::endl;
    printClockData<std::chrono::system_clock>();
 
    std::cout << "\nhigh_resolution_clock: " << std::endl;
    printClockData<std::chrono::high_resolution_clock>();
 
    std::cout << "\nsteady_clock: " << std::endl;
    printClockData<std::chrono::steady_clock>();
}
  • 结果如下图所示:
    • 可以看到,system_clock和high_resolution_clock有着相同精度,100纳秒,而steady_clock的精度则是毫秒
    • 还可以看到,steady_clock和high_resolution_clock不能被调整
    • 还需要注意,在其他系统中情况也许完全不同,例如high_resolution_clock有可能和system_clock相同

演示案例

  • 用来比较程序的两个时间点是否相同,或计算差距,system_clock扮演者重要角色。例如:
//获得当前时间,保存为system_start
auto system_start = std::chrono::system_clock::now();
 
//...
 
//再次获取当前时间,与system_start进行比较,查看时间是否超过了1分钟
if (std::chrono::system_clock::now() > system_start + std::chrono::minutes(1))
{
    //...
}
  • 上面的代码可能行不通,因为如果clock在中间被调整,即使程序执行超过了1分钟,if也可能返回false
  • 类似的,当我们处理程序的执行时间,下面的程序有可能因中间clock被调用而打印出一个负值duration
#include <windows.h> //Sleep
 
int main()
{
    //获得当前时间,保存为system_start
    auto system_start = std::chrono::system_clock::now();
 
    Sleep(3000); //休息3秒
 
    //获得时间差
    auto diff = std::chrono::system_clock::now() - system_start;
    //强制转换为秒
    auto sec = std::chrono::duration_cast<std::chrono::seconds>(diff);
    //打印
    std::cout << "this program runs:" << sec.count() << " seconds" << std::endl;
}

  • 基于相同的理由,使用timer搭配steady_clock以外的lock,有可能一旦system_clock被调用它们的duration也随之改变

五、Timepoint(时间点)

  • Timepoint(时间点)
    • 表现出某个特定时间点,关联至某个clock的某个正值或负值duration
    • 例如,如果duration是10天,其关联的clock epoch是“1970年1月1日”,那么这个timepoint就是1970年1月11日
    • timepoint提供的能力包括:产出epoch、产出“与其clock相应”的所有timepoint中的最小值和最大值,以及timepoint的各种算术运算
  • timepoint定义如下:

  • timepoint分为下面四种类型:
    • Epoch:由任何clock的time_point的默认构造函数产出
    • Current time:由任何clock的static成员函数now()产出
    • Minimum timepoint:由任何clock的time_point的static成员函数min()产出
    • Maximum timepoint:由任何clock的time_point的static成员函数max()产出

演示案例1

  • 下面程序将timepoint赋值给tp并转换为日历表示法,然后打印出来:
std::string asString(const std::chrono::system_clock::time_point& tp)
{
    std::time_t t = std::chrono::system_clock::to_time_t(tp);
    std::string ts = std::ctime(&t);
    ts.resize(ts.size() - 1);
    return ts;
}
 
int main()
{
    std::chrono::system_clock::time_point tp;
    std::cout << "epoch: " << asString(tp) << std::endl;
 
    tp = std::chrono::system_clock::now();
    std::cout << "now: " << asString(tp) << std::endl;
 
    tp = std::chrono::system_clock::time_point::min();
    std::cout << "min: " << asString(tp) << std::endl;
 
    tp = std::chrono::system_clock::time_point::max();
    std::cout << "max: " << asString(tp) << std::endl;
}

timepoint提供的操作

  • timepoint提供的操作如下:

  • time_since_epoch()
    • 一般而言,timepoint对象只有一个成员,是个duration,与相应的clock的epoch有着关系
    • 这个timepoint值可以通过time_since_epoch()取得

timepoint的溢出

  • 虽然timepoint的接口用了ratio,这确保了duration单元的溢出会导致编译器报错,但是duration的溢出还是可能会发生
  • 见下面的例子:

  • 这也说明了chrono是一个duration/timepoint程序库,而不是个date/time程序库。你可以计算duration和timepoint但仍然必须把epoch、最小和最大的timepoint、闰年和闰秒纳入考虑

六、C和POSIX提供的Date/Time函数

  • C和POSIX提供的Date/Time函数介绍参阅:https://blog.csdn.net/qq_41453285/article/details/102651298
  • C++标准也提供了C和POSIX所提供的“处理date和time”接口。原本在<time.h>内的宏、类型、函数,现在被定义在<ctime>的namespace std内
  • <ctime>所提供的操作如下图所示:
    • 宏CLOCK_PER_SEC定义了clock()的单位类型(它返回的是elapsed CPU time,以1/CLOCK_PER_SEC秒计)
    • time_t通常只是“始自UNIX epoch(1970年1月1日)的秒数”。然而根据C和C++标准的说法,也并不保证如此

演示案例(timepoint和日历时间的转换)

七、以计时器停滞线程

  • Duration和timepoint可用于线程或程序(即主线程)的停滞(block)。停滞可以是无条件的,也可以指定最大时间段,或等待一个lock或某条件成立,或等待另一线程结束
  • 提供的操作如下:
    • sleep_for()和sleep_until():由this_thead提供用以停滞线程
    • try_lock_for()和try_lock_until():用来在等待一个mutex时指定最大时间段
    • wait_for()和wait_until():用来在等待某条件成立或等待一个future时指定最大时间段
  • 所有以...for()结尾的函数都会用到一个duration,所有以...until()结束的函数都会用到一个timepoint。例如:
    • this_thread::sleep_for(chrono::seconds(10));   //会停滞当前线程(不无可能是主线程)10秒钟
    • this_thread::sleep_until(chrono::system_clock::now()+chrono::seconds(10));  //停滞当前线程,直到system clock 来到一个“比此刻多10秒”的timepoint
  • 这些调用虽然看起来相同,其实不然:
    • 所有...until()函数,你需要传递一个timepoint,而它收到时间调整的影响。如果sleep_until()之后的10秒内system clock被调整了,停滞时间也会被相应调整。例如我们把system clock回调1小时,这个程序将被停滞60分钟又10秒。又如果我们把clock调快超过10秒,timer会立刻结束
    • 如果使用...for()函数如sleep_for(),你必须传一个duration,或你使用steady_clock,那么system clock的调整通常不会影响duration。然而在一个不提供steady clock的硬件上,软件平台没机会在不受到“可能被调整的system time”的影响下计算秒数,因此时间的调整也会冲击...for()函数
  • 所有这些timer都不会保证绝对精准。对任何timer而言都存在一点点延迟,因为系统只是周期性地检查那个timer结束了,而timer和interrupt(中断)的处理又需要花费一些时间。因此,timer的时间长度将会是它们所指定的时间加上一小段(取决于实现的质量和当下情势)
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值