muduo网络库学习笔记(10):定时器的实现
传统的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):定时器的实现的更多相关文章
- muduo网络库学习笔记(三)TimerQueue定时器队列
目录 muduo网络库学习笔记(三)TimerQueue定时器队列 Linux中的时间函数 timerfd简单使用介绍 timerfd示例 muduo中对timerfd的封装 TimerQueue的结 ...
- muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制
目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...
- muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor
目录 muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor Connector 系统函数connect 处理非阻塞connect的步骤: Connetor时序图 Accep ...
- muduo 网络库学习之路(一)
前提介绍: 本人是一名大三学生,主要使用C++开发,兴趣是高性能的服务器方面. 网络开发离不开网络库,所以今天开始学一个新的网络库,陈老师的muduo库 我参考的书籍就是陈老师自己关于muduo而编著 ...
- muduo网络库学习之MutexLock类、MutexLockGuard类、Condition类、CountDownLatch类封装中的知识点
一.MutexLock 类 class MutexLock : boost::noncopyable 二.MutexLockGuard类 class MutexLockGuard : bo ...
- Linux多线程服务端编程 使用muduo C++网络库 学习笔记 日志log
代码来自陈硕开源代码库 muduo中 地址是https://github.com/chenshuo/muduo #pragma once #include <string> #define ...
- muduo网络库架构总结
目录 muduo网络库简介 muduo网络库模块组成 Recator反应器 EventLoop的两个组件 TimerQueue定时器 Eventfd Connector和Acceptor连接器和监听器 ...
- muduo网络库源码学习————Timestamp.cc
今天开始学习陈硕先生的muduo网络库,moduo网络库得到很多好评,陈硕先生自己也说核心代码不超过5000行,所以我觉得有必要拿过来好好学习下,学习的时候在源码上面添加一些自己的注释,方便日后理解, ...
- python网络爬虫学习笔记
python网络爬虫学习笔记 By 钟桓 9月 4 2014 更新日期:9月 4 2014 文章文件夹 1. 介绍: 2. 从简单语句中開始: 3. 传送数据给server 4. HTTP头-描写叙述 ...
随机推荐
- opencv 中文文档地址
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/tutorials.html
- cat、cp命令
cat是查看文件内容, cp –cp是连目录及件文件都拷贝 cp是拷贝文件 a.txt里的内容是, abc def ghi cat a.txt |grep –v ghi 得到结果, abc def h ...
- 手机终于能连接android studio
折腾了三天,终于能连上了,网上各种方法都试了,就是不行,结果安装了腾迅安全管家,自动安装了手机驱动就好了,原来一直都是驱动惹的祸啊......
- 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 ...
- php 生成mysql数据字典代码
由于项目开发用了比较多的表 ,为了快速获取数据字典,通过php代码的方式来获取表结构和表注释.代码如下: <?php /** * 生成mysql数据字典 */ header ( "Co ...
- Java NIO使用及原理分析(二)
在第一篇中,我们介绍了NIO中的两个核心对象:缓冲区和通道,在谈到缓冲区时,我们说缓冲区对象本质上是一个数组,但它其实是一个特殊的数组,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况,如 ...
- 将内容重定向到剪切板(clip.exe)
Add-Type -Assembly PresentationCore[Windows.Clipboard]::SetText("abc中文def")先用 $output | Ou ...
- text code
https://github.com/Itseez/opencv/blob/master/modules/objdetect/src/erfilter.cpp
- Cloud Foundry中gorouter对StickySession的支持
Cloud Foundry作为业界出众的PaaS平台,在应用的可扩展性方面做得很优秀. 详细来讲,在一个应用须要横向伸展的时候,Cloud Foundry能够轻松地帮助用户做好伸展工作,也就是创建出一 ...
- hdu 3586 Information Disturbing(树形dp + 二分)
本文出自 http://blog.csdn.net/shuangde800 题目链接: hdu-3586 题意 给一棵n个节点的树,节点编号为1-n,根节点为1.每条边有权值,砍掉一条边要花费 ...