传统的Reactor通过控制select和poll的等待时间来实现定时,而现在在Linux中有了timerfd,我们可以用和处理IO事件相同的方式来处理定时,代码的一致性更好。

一、为什么选择timerfd
常见的定时函数有如下几种:

sleep
alarm
usleep
nanosleep
clock_nanosleep
getitimer / setitimer
timer_create / timer_settime / timer_gettime / timer_delete
timerfd_create / timerfd_gettime / timerfd_settime
1
2
3
4
5
6
7
8
9
我们之所以选择timerfd,是因为:
1.sleep / alarm / usleep 在实现时有可能用了信号 SIGALRM,在多线程程序中处理信号是个相当麻烦的事情,应当尽量避免。
2.nanosleep 和 clock_nanosleep 是线程安全的,但是在非阻塞网络编程中,绝对不能用让线程挂起的方式来等待一段时间,程序会失去响应。正确的做法是注册一个时间回调函数。
3.getitimer 和 timer_create 也是用信号来传递超时,在多线程程序中也会有麻烦。
4.timer_create 可以指定信号的接收方是进程还是线程,算是一个进步,不过在信号处理函数(signal handler)能做的事情实在很受限。
5.timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件,这也正是 Reactor 模式的长处。

timerfd相关函数介绍:

#include <sys/timerfd.h>

/**
* 此函数用于创建一个定时器文件
* 参数clockid可以是CLOCK_MONOTONIC或者CLOCK_REALTIME
* 参数flags可以是0或者TFD_CLOEXEC/TFD_NONBLOCK
* 函数返回值是一个文件句柄fd
*/
int timerfd_create(int clockid, int flags);

/**
* 此函数用于设置新的超时时间,并开始计时
* 参数fd是timerfd_create返回的文件句柄
* 参数flags为TFD_TIMER_ABSTIME(1)代表设置的是绝对时间;为0代表相对时间
* 参数new_value为需要设置的超时和间隔时间
* 参数old_value为定时器这次设置之前的超时时间
* 函数返回0代表设置成功
*/
int timerfd_settime(int fd, int flags, const struct itimerspec *new_value, struct itimerspec *old_value);

/**
* 此函数用于获得定时器距离下次超时还剩下的时间
* 如果调用时定时器已经到期,并且该定时器处于循环模式
* 即设置超时时间时struct itimerspec::it_interval不为0
* 那么调用此函数之后定时器重新开始计时
*/
int timerfd_gettime(int fd, struct itimerspec *curr_value);

itimerspec结构体;
struct itimerspec {
struct timespec it_interval; //interval for periodic timer
struct timespec it_value; //initial expiration
};

struct timespec {
time_t tv_sec; //seconds
long tv_nsec; //nano-seconds
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
二、muduo定时器的实现
muduo的定时器功能由三个class实现,TimerId、Timer、TimerQueue,用户只能看到第一个class,另外两个都是内部实现细节。

TimerId被设计用来取消Timer的,它的结构很简单,只有一个Timer指针和其序列号。其中还声明了TimerQueue为其友元,可以操作其私有数据。

Timer是对定时器的高层次抽象,封装了定时器的一些参数,例如超时回调函数、超时时间、超时时间间隔、定时器是否重复、定时器的序列号。其函数大都是设置这些参数,run()用来调用回调函数,restart()用来重启定时器(如果设置为重复)。

重点介绍一下TimerQueue类。

三、TimerQueue class
TimerQueue的接口很简单,只有两个函数addTimer()和cancel()。它的内部有channel,和timerfd相关联。添加新的Timer后,在超时后,timerfd可读,会处理channel事件,之后调用Timer的回调函数;在timerfd的事件处理后,还有检查一遍超时定时器,如果其属性为重复还有再次添加到定时器集合中。

时序图:
这里写图片描述

(1)TimerQueue数据结构的选择
TimerQueue需要高效地组织目前尚未到期的Timer,能快速地根据当前时间找到已经到期的Timer,也要能高效地添加和删除Timer。因而可以用二叉搜索树(例如std::set/std::map),把Timer按到期时间先后排好序,其操作的复杂度是O(logN),但我们使用时还要处理两个Timer到期时间相同的情况(map不支持key相同的情况),做法如下:

//两种类型的set,一种按时间戳排序,一种按Timer的地址排序
//实际上,这两个set保存的是相同的定时器列表

typedef std::pair<Timestamp, Timer*> Entry;
typedef std::set<Entry> TimerList;

typedef std::pair<Timer*, int64_t> ActiveTimer;
typedef std::set<ActiveTimer> ActiveTimerSet;
1
2
3
4
5
6
7
8
(2)代码分析

TimerQueue.h

#ifndef MUDUO_NET_TIMERQUEUE_H
#define MUDUO_NET_TIMERQUEUE_H

#include <set>
#include <vector>

#include <boost/noncopyable.hpp>

#include <muduo/base/Mutex.h>
#include <muduo/base/Timestamp.h>
#include <muduo/net/Callbacks.h>
#include <muduo/net/Channel.h>

namespace muduo
{
namespace net
{

class EventLoop;
class Timer;
class TimerId;

class TimerQueue : boost::noncopyable
{
public:
TimerQueue(EventLoop* loop);
~TimerQueue();

//一定是线程安全的,可以跨线程调用。通常情况下被其它线程调用。
TimerId addTimer(const TimerCallback& cb,
Timestamp when,
double interval);

void cancel(TimerId timerId);

private:

//FIXME: use unique_ptr<Timer> instead of raw pointers.
//unique_ptr是C++ 11标准的一个独享所有权的智能指针
//无法得到指向同一对象的两个unique_ptr指针
//但可以进行移动构造与移动赋值操作,即所有权可以移动到另一个对象(而非拷贝构造)
typedef std::pair<Timestamp, Timer*> Entry;
typedef std::set<Entry> TimerList;
typedef std::pair<Timer*, int64_t> ActiveTimer;
typedef std::set<ActiveTimer> ActiveTimerSet;

//以下成员函数只可能在其所属的I/O线程中调用,因而不必加锁。
//服务器性能杀手之一是锁竞争,所以要尽可能少用锁
void addTimerInLoop(Timer* timer);
void cancelInLoop(TimerId timerId);
// called when timerfd alarms
void handleRead();
//返回超时的定时器列表
std::vector<Entry> getExpired(Timestamp now);
void reset(const std::vector<Entry>& expired, Timestamp now);

bool insert(Timer* timer);
EventLoop* loop_; //所属的EventLoop
const int timerfd_;
Channel timerfdChannel_;
TimerList timers_; //timers_是按到期时间排序

//for cancel()
//timers_与activeTimers_保存的是相同的数据
//timers_是按到期时间排序,activeTimers_是按对象地址排序
ActiveTimerSet activeTimers_;
bool callingExpiredTimers_; /* atomic */ //是否正在处理超时事件
ActiveTimerSet cancelingTimers_; //保存的是被取消的定时器
};

}
}
#endif //MUDUO_NET_TIMERQUEUE_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
TimerQueue.cc

#define __STDC_LIMIT_MACROS
#include <muduo/net/TimerQueue.h>

#include <muduo/base/Logging.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/Timer.h>
#include <muduo/net/TimerId.h>

#include <boost/bind.hpp>

#include <sys/timerfd.h>

namespace muduo
{
namespace net
{
namespace detail
{

//创建定时器,用到了www.ycyc66.cn timerfd_create()
int createTimerfd()
{
int timerfd = ::timerfd_create(CLOCK_MONOTONIC,
TFD_NONBLOCK | TFD_CLOEXEC);
if (timerfd < 0)
{
LOG_SYSFATAL << "www.honqili66.com Failed in timerfd_create";
}
return timerfd;
}

//计算超时时刻与当前时间的时间差
struct timespec howMuchTimeFromNow(Timestamp when)
{
int64_t microseconds = when.microSecondsSinceEpoch()
- Timestamp::now().microSecondsSinceEpoch();
//精确度没有达到那么高,所以小于100时都置为100
if (microse www.xbyl688.com conds < 100)
{
microseconds = 100;
}
struct timespec ts;
ts.tv_sec = static_cast<time_t>(
microseconds / Timestamp::kMicroSecondsPerSecond);
ts.tv_nsec = static_cast<long>(
(microseconds % www.senta77.com Timestamp::kMicroSecondsPerSecond) * 1000);
return ts;
}

//处理超时事件。超时后,timerfd变为可读
void readTimerfd(int timerfd, Timestamp now)
{
uint64_t howmany; //howmany为超时次数
ssize_t n = ::read(timerfd, &howmany, sizeof howmany);
LOG_TRACE << "TimerQueue::handleRead() " << howmany << " at " << now.toString();
if (n != sizeof howmany)
{
LOG_ERROR << "TimerQueue::handleRead() reads " << n << " bytes instead of 8";
}
}

//重置定时器的超时时间,用到了timerfd_settime()
void resetTimerfd(int www.tyff688.com timerfd, Timestamp expiration)
{
//wake up loop by timerfd_settime()
struct itimerspec newValue;
struct itimerspec oldValue;
bzero(&newValue, sizeof newValue);
bzero(&oldValue, sizeof oldValue);
newValue.it_value = howMuchTimeFromNow(expiration);
int ret = ::timerfd_settime(timerfd, 0, &newValue, &oldValue);
if (ret)
{
LOG_SYSERR << "timerfd_settime()";
}
}

}
}
}

using namespace muduo;
using namespace muduo::net;
using namespace muduo::net::detail;

//构造函数
TimerQueue::TimerQueue(EventLoop* loop)
: loop_(loop),
timerfd_(createTimerfd()), //创建timerfd
timerfdChannel_(loop, timerfd_), //timerfd相关的channel
timers_(),
callingExpiredTimers_(false)
{
timerfdChannel_.setReadCallback(
boost::bind(&TimerQueue::handleRead, this));
// we are always reading the timerfd, we disarm it with timerfd_settime.
timerfdChannel_.enableReading(); //timerfd对应的channel监听事件为可读事件
}

//析构函数
TimerQueue::~TimerQueue()
{
::close(timerfd_);
// do not remove channel, since we're in EventLoop::dtor();
for (TimerList::iterator it = timers_.begin();
it != timers_.end(); ++it)
{
delete it->second; //手动释放Timer*
}
}

//添加新的定时器
TimerId TimerQueue::addTimer(const TimerCallback& cb,
Timestamp when,
double interval)
{
Timer* timer = new Timer(cb, when, interval);
addTimerInLoop(timer);
return TimerId(timer, timer->sequence());
}

//取消定时器
void TimerQueue::cancel(TimerId timerId)
{
cancelInLoop(timerId);
}

//添加定时器时实际调用了addTimerInLoop()
void TimerQueue::addTimerInLoop(Timer* timer)
{
loop_->assertInLoopThread();
//插入一个定时器,有可能会使得最早到期的定时器发生改变
bool earliestChanged = insert(timer);

if (earliestChanged)
{
//重置定时器的超时时刻(timerfd_settime)
resetTimerfd(timerfd_, timer->expiration());
}
}

//取消定时器时实际调用了addTimerInLoop()
void TimerQueue::cancelInLoop(TimerId timerId)
{
loop_->assertInLoopThread();
assert(timers_.size() == activeTimers_.size());
ActiveTimer timer(timerId.timer_, timerId.sequence_); //要取消的定时器timer
//查找该定时器
ActiveTimerSet::iterator it = activeTimers_.find(timer);
//要取消的在当前激活的Timer集合中
if (it != activeTimers_.end())
{
size_t n = timers_.erase(Entry(it->first->expiration(), it->first)); //从timers_中取消
assert(n == 1); (void)n;
delete it->first; //FIXME:如果用了unique_ptr,这里就不需要手动删除了
activeTimers_.erase(it); //从activeTimers_中取消
}
//如果正在执行超时定时器的回调函数,则加入到cancelingTimers集合中
else if (callingExpiredTimers_)
{
cancelingTimers_.insert(timer);
}
assert(timers_.size() == activeTimers_.size());
}

void TimerQueue::handleRead()
{
loop_->assertInLoopThread();
Timestamp now(Timestamp::now());
readTimerfd(timerfd_, now); //读timerfd

//获取该时刻之前所有的定时器列表(即超时定时器列表)
std::vector<Entry> expired = getExpired(now);

callingExpiredTimers_ = true;
cancelingTimers_.clear();

for (std::vector<Entry>::iterator it = expired.begin();
it != expired.end(); ++it)
{
//这里回调定时器处理函数
it->second->run();
}
callingExpiredTimers_ = false;

//把重复的定时器重新加入到定时器中
reset(expired, now);
}

//rvo即Return Value Optimization
//是一种编译器优化技术,可以把通过函数返回创建的临时对象给”去掉”
//然后可以达到少调用拷贝构造的操作
std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
{
assert(timers_.size() == activeTimers_.size());
std::vector<Entry> expired;
//UINTPTR_MAX表示最大的地址
Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));
//返回第一个未到期的Timer的迭代器
//lower_bound的含义是返回第一个值>=sentry的元素的iterator
//即*end >= sentry,从而end->first > now
//注意:此处是>,而不是>=
TimerList::iterator end = timers_.lower_bound(sentry);
assert(end == timers_.end() || now < end->first);
//[begin end)之间的元素(到期的)追加到expired末尾
std::copy(timers_.begin(), end, back_inserter(expired));
//从timers_中移除到期的定时器
timers_.erase(timers_.begin(), end);

//从activeTimers_中移除到期的定时器
for (std::vector<Entry>::iterator it = expired.begin();
it != expired.end(); ++it)
{
ActiveTimer timer(it->second, it->second->sequence());
size_t n = activeTimers_.erase(timer);
assert(n == 1); (void)n;
}

assert(timers_.size() == activeTimers_.size());
return expired;
}

void TimerQueue::reset(const std::vector<Entry>& expired, Timestamp now)
{
Timestamp nextExpire;

for (std::vector<Entry>::const_iterator it = expired.begin();
it != expired.end(); ++it)
{
ActiveTimer timer(it->second, it->second->sequence());
//如果是重复的定时器并且不在cancelingTimers_集合中,则重启该定时器
if (it->second->repeat()
&& cancelingTimers_.find(timer) == cancelingTimers_.end())
{
it->second->restart(now);
insert(it->second);
}
else
{
//一次性定时器或者已被取消的定时器是不能重置的,因此删除该定时器
//FIXME move to a free list
delete it->second; //FIXME: no delete please
}
}

if (!timers_.empty())
{
//获取最早到期的定时器超时时间
nextExpire = timers_.begin()->second->expiration();
}

if (nextExpire.valid())
{
//重置定时器的超时时刻(timerfd_settime)
resetTimerfd(timerfd_, nextExpire);
}
}

//插入一个timer
bool TimerQueue::insert(Timer* timer)
{
loop_->assertInLoopThread();
assert(timers_.size() == activeTimers_.size());
bool earliestChanged = false;
Timestamp when = timer->expiration();
TimerList::iterator it = timers_.begin();
//如果timers_为空或者when小于timers_中的最早到期时间
if (it == timers_.end() || when < it->first)
{
earliestChanged = true;
}
{
//插入到timers_中
std::pair<TimerList::iterator, bool> result
= timers_.insert(Entry(when, timer));
assert(result.second); (void)result;
}
{
//插入到activeTimers_中
std::pair<ActiveTimerSet::iterator, bool> result
= activeTimers_.insert(ActiveTimer(timer, timer->sequence()));
assert(result.second); (void)result;
}

assert(timers_.size() == activeTimers_.size());
return earliestChanged;

muduo网络库学习笔记(10):定时器的实现的更多相关文章

  1. muduo网络库学习笔记(三)TimerQueue定时器队列

    目录 muduo网络库学习笔记(三)TimerQueue定时器队列 Linux中的时间函数 timerfd简单使用介绍 timerfd示例 muduo中对timerfd的封装 TimerQueue的结 ...

  2. muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制

    目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...

  3. muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor

    目录 muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor Connector 系统函数connect 处理非阻塞connect的步骤: Connetor时序图 Accep ...

  4. muduo 网络库学习之路(一)

    前提介绍: 本人是一名大三学生,主要使用C++开发,兴趣是高性能的服务器方面. 网络开发离不开网络库,所以今天开始学一个新的网络库,陈老师的muduo库 我参考的书籍就是陈老师自己关于muduo而编著 ...

  5. muduo网络库学习之MutexLock类、MutexLockGuard类、Condition类、CountDownLatch类封装中的知识点

    一.MutexLock 类 class  MutexLock  :  boost::noncopyable 二.MutexLockGuard类 class  MutexLockGuard  :  bo ...

  6. Linux多线程服务端编程 使用muduo C++网络库 学习笔记 日志log

    代码来自陈硕开源代码库 muduo中 地址是https://github.com/chenshuo/muduo #pragma once #include <string> #define ...

  7. muduo网络库架构总结

    目录 muduo网络库简介 muduo网络库模块组成 Recator反应器 EventLoop的两个组件 TimerQueue定时器 Eventfd Connector和Acceptor连接器和监听器 ...

  8. muduo网络库源码学习————Timestamp.cc

    今天开始学习陈硕先生的muduo网络库,moduo网络库得到很多好评,陈硕先生自己也说核心代码不超过5000行,所以我觉得有必要拿过来好好学习下,学习的时候在源码上面添加一些自己的注释,方便日后理解, ...

  9. python网络爬虫学习笔记

    python网络爬虫学习笔记 By 钟桓 9月 4 2014 更新日期:9月 4 2014 文章文件夹 1. 介绍: 2. 从简单语句中開始: 3. 传送数据给server 4. HTTP头-描写叙述 ...

随机推荐

  1. Log接口的重新封装

    闲来没事,看见当前的项目的日志形式有点冗余,每个类都需要声明确实有点繁琐, 因此重新将logback重新封装一下,供整个工程共享使用,版本是1.0.9. 代码如下: import java.lang. ...

  2. RestService中的 get post put delete

    HTTP 定义了与服务器交互的不同方法,最基本的有四种方法:GET,POST,PUT,DELETE.URL即资源描述符,我们可以这样认为:一个URL地址, 用于描述一个网络上的资源,而HTTP中的GE ...

  3. CentOS6.6从头到尾部署nginx与tomcat多实例 转

    前提条件: 1.需要一个全新的centos系统(本文中用到是centos6.6) 2.vmware虚拟机 3.vmware下安装centos系统,以NAT方式与宿主机相连 4.在centos系统中pi ...

  4. 深入理解Objective-C:优化你的代码

    开篇 只要用到Objective-C,我们每天都会跟方法调用打交道.我们都知道Objective-C的方法决议是动态的,但是在底层一个方法究竟是怎么找到的,方法缓存又是怎么运作的却鲜为人知. 本文主要 ...

  5. Android开发环境搭建(图文教程)

    昨天又搭建了一次Android的开发环境,尝试了好几种方式,也遇到了一些问题,在此分享一下. 注意:官网公布的最新版本号的SDK和ADT(23.0.0),对于和Eclipse集成的开发环境是有BUG存 ...

  6. FastDFS分布文件系统[转]

    FastDFS是为互联网应用量身定做的一套分布式文件存储系统,非常适合用来存储用户图片.视频.文档等文件.对于互联网应用,和其他分布式文件系统相比,优势非常明显.具体情况大家可以看相关的介绍文档,包括 ...

  7. Quartz Enterprise Job Scheduler 1.x Tutorial---转载

    Lesson 10: Configuration, Resource Usage and SchedulerFactory http://www.quartz-scheduler.org/docume ...

  8. JavaScript 应用开发 #1:理解模型与集合

    在 < Backbone 应用实例 > 这个课程里面,我们会一起用 JavaScript 做一个小应用,它可以管理任务列表,应用可以创建新任务,编辑还有删除任务等等.这个实例非常好的演示了 ...

  9. FolderBrowserDialog使用

    private void button_browse_Click(object sender, EventArgs e) { FolderBrowserDialog fbd = new FolderB ...

  10. Linq保留字含义

    using System; using System.Query; using System.Collections.Generic; class app { static void Main() { ...