传统的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. opencv 中文文档地址

    http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/tutorials.html

  2. cat、cp命令

    cat是查看文件内容, cp –cp是连目录及件文件都拷贝 cp是拷贝文件 a.txt里的内容是, abc def ghi cat a.txt |grep –v ghi 得到结果, abc def h ...

  3. 手机终于能连接android studio

    折腾了三天,终于能连上了,网上各种方法都试了,就是不行,结果安装了腾迅安全管家,自动安装了手机驱动就好了,原来一直都是驱动惹的祸啊......

  4. The server does not support version 3.1 of the JEE Web module specification.

    使用MyEclipse2015打开MyEclipse2014编辑的项目,在服务器Tomcat 7部署时,提示"The server does not support version 3.1 ...

  5. php 生成mysql数据字典代码

    由于项目开发用了比较多的表 ,为了快速获取数据字典,通过php代码的方式来获取表结构和表注释.代码如下: <?php /** * 生成mysql数据字典 */ header ( "Co ...

  6. Java NIO使用及原理分析(二)

    在第一篇中,我们介绍了NIO中的两个核心对象:缓冲区和通道,在谈到缓冲区时,我们说缓冲区对象本质上是一个数组,但它其实是一个特殊的数组,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况,如 ...

  7. 将内容重定向到剪切板(clip.exe)

    Add-Type -Assembly PresentationCore[Windows.Clipboard]::SetText("abc中文def")先用 $output | Ou ...

  8. text code

    https://github.com/Itseez/opencv/blob/master/modules/objdetect/src/erfilter.cpp

  9. Cloud Foundry中gorouter对StickySession的支持

    Cloud Foundry作为业界出众的PaaS平台,在应用的可扩展性方面做得很优秀. 详细来讲,在一个应用须要横向伸展的时候,Cloud Foundry能够轻松地帮助用户做好伸展工作,也就是创建出一 ...

  10. hdu 3586 Information Disturbing(树形dp + 二分)

    本文出自   http://blog.csdn.net/shuangde800 题目链接:   hdu-3586 题意 给一棵n个节点的树,节点编号为1-n,根节点为1.每条边有权值,砍掉一条边要花费 ...