muduo_base 01 (Timestamp)

本文源码基于muduo v2.0.2分析

类图

muduo_timestamp.puml

通读源码之前的几点说明

class Timestamp : public muduo::copyable,
                  public boost::equality_comparable<Timestamp>,
                  public boost::less_than_comparable<Timestamp>

Timestamp继承的3个基类,下面将分别介绍

copyable & nocopyable

值语义:可以拷贝,拷贝之后与原对象脱离关系

对象语义:要么不能拷贝,要么可以拷贝,拷贝之后与原对象仍然有联系,比如共享底层资源(要实现自己的拷贝构造函数)

class copyable
{
 protected:
  copyable() = default;
  ~copyable() = default;
};

class noncopyable
{
 public:
  noncopyable(const noncopyable&) = delete;
  void operator=(const noncopyable&) = delete;

 protected:
  noncopyable() = default;
  ~noncopyable() = default;
};

equality_comparable & less_than_comparable

头文件boost/operators.hpp

要派生自 boost::less_than_comparable, 派生类(T)必须提供:

bool operator<(const T&, const T&);

less_than_comparable 将依照 operator< 实现其余的三个操作符

bool operator>(const T&,const T&);
bool operator<=(const T&,const T&);
bool operator>=(const T&,const T&);

同理,要派生自 boost::equality_comparable, 派生类(T)必须提供:

bool operator==(const T&,const T&);

将自动完成bool operator!=(const T&,const T&);

静态断言static_assert

static_assert(sizeof(Timestamp) == sizeof(int64_t),
              "Timestamp is same size as int64_t");

编译时断言,条件为true不打印信息,false时打印

多平台PRid64占位

snprintf(buf, sizeof(buf)-1, "%" PRId64 ".%06" PRId64 "", seconds, microseconds);

64位类型的printf占位符是ld,而32位是lld。为适应32位和64位的系统,使用了PRId64

PRId64位于头文件inttypes.h

# if __WORDSIZE == 64
#  define __PRI64_PREFIX	"l"
# else
#  define __PRI64_PREFIX	"ll"
# endif

# define PRId64		__PRI64_PREFIX "d"

同时,inttypes头文件可能需要定义宏__STDC_FORMAT_MACROS后才能include,具体根据编译器

#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#include <inttypes.h>
#undef __STDC_FORMAT_MACROS
#endif

stackoverflow 讨论

通读源码

Timestamp整体实现比较简单。从静态函数now()开始看起,其余函数围绕now得到的时间,进行格式化输出等操作。

Timestamp Timestamp::now()
{
  struct timeval tv;
  gettimeofday(&tv, NULL);
  int64_t seconds = tv.tv_sec;
  return Timestamp(seconds * kMicroSecondsPerSecond + tv.tv_usec);
}

gettimeofday原型

#include <sys/time.h>

int gettimeofday(struct timeval *restrict tv,
                struct timezone *restrict tz);
int settimeofday(const struct timeval *tv,
                const struct timezone *tz);
/*
struct timeval {
        time_t      tv_sec;     /* seconds */
        suseconds_t tv_usec;    /* microseconds */
    };
struct timezone {
        int tz_minuteswest;     /* minutes west of Greenwich */
        int tz_dsttime;         /* type of DST correction */
    };

@ret: return 0 for success.On error, -1 is returned and errno is set to indicate the error.
*/

gettimeofday是C库提供的函数,其封装了sys_gettimeofday系统调用。但在x64结构上,gettimeofday可以不通过系统调用拿到系统时间。先看2个概念:

  1. 墙上时间:即实际时间(1970/1/1号以来的时间),它是由我们主板电池供电的(装过PC机的同学都了解)RTC单元存储的,这样即使机器断电了时间也不用重设。当操作系统启动时,会用这个RTC来初始化墙上时间,接着,内核会在一定精度内根据jiffies维护这个墙上时间。

  2. jiffies:就是操作系统启动后经过的时间,它的单位是节拍数。有些体系架构,1个节拍数是10ms,但我们常用的x86体系下,1个节拍数是1ms。也就是说,jiffies这个全局变量存储了操作系统启动以来共经历了多少毫秒。

x64下do_gettimeofday

void do_gettimeofday(struct timeval *tv)
{
	unsigned long seq, t;
 	unsigned int sec, usec;
 
	do {
		seq = read_seqbegin(&xtime_lock);
 
		sec = xtime.tv_sec;
		usec = xtime.tv_nsec / 1000;
		t = (jiffies - wall_jiffies) * (1000000L / HZ) +
			do_gettimeoffset();
		usec += t;
 
	} while (read_seqretry(&xtime_lock, seq));
 
	tv->tv_sec = sec + usec / 1000000;
	tv->tv_usec = usec % 1000000;
}

可以看出是通过xtime和jiffies共同得出的tv,没有经过系统调用。 更多的讨论请看浅谈时间函数gettimeofday的成本-陶辉

其他函数不再讨论

unittest代码

benchmark部分:

调用Timestamp::now()填充一个大小为1M的std::vector,并且打印出小于100ms的延迟分布情况