muduo定时器、多线程模型及epoll的封装
timerfd是Linux为用户程序提供的一个定时器接口,这个接口基于文件描述符。
clock_gettime函数可以获取系统时钟,精确到纳秒。需要在编译时指定库:-lrt。可以获取两种类型时间:
timerfd接口:
int timerfd_create(int clockid, int flags);
timerfd_create生成一个定时器对象,返回与之关联的文件描述符
参数clockid可以是CLOCK_MONOTONIC或者CLOCK_REALTIME。
参数flags可以是0或者O_CLOEXEC/O_NONBLOCK。
函数返回值是一个文件句柄fd。
int timerfd_settime(int ufd, int flags, const struct itimerspec * utmr, struct itimerspec * otmr);
timerfd_settime函数用于设置新的超时时间,并开始计时
参数ufd是timerfd_create返回的文件句柄。
参数flags为TFD_TIMER_ABSTIME代表设置的是绝对时间;为0代表相对时间。
参数utmr为超时时间
参数otmr为定时器这次设置之前的超时时间。
函数返回0代表设置成功。
int timerfd_gettime(int ufd, struct itimerspec * otmr);
timerfd_gettime函数用于获得定时器距离下次超时还剩下的时间。如果调用时定时器已经到期,并且该定时器处于循环模式(设置超时时间时struct itimerspec::it_interval不为0),那么调用此函数之后定时器重新开始计时。
read
当timerfd为阻塞方式时,read函数将被阻塞,直到定时器超时。
函数返回值大于0,代表定时器超时;否则,代表没有超时
数据结构:
struct timespec { time_t tv_sec; /* Seconds */ long tv_nsec; /* Nanoseconds */ }; struct itimerspec { struct timespec it_interval; /* Interval for periodic timer */ struct timespec it_value; /* Initial expiration */ };
it_value是首次超时时间,需要填写从clock_gettime获取的时间,并加上要超时的时间。 it_interval是后续周期性超时时间,即如果是周期性定时器,第一个超时时间是 it_value,后面所有周期的超时时间为it_interval
#include <sys/timerfd.h> #include <sys/time.h> #include <time.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <stdint.h> /* Definition of uint64_t */ #define handle_error(msg) \ do { perror(msg); exit(EXIT_FAILURE); } while (0) void printTime() { struct timeval tv; gettimeofday(&tv, NULL); printf("printTime: current time:%ld.%ld ", tv.tv_sec, tv.tv_usec); } int main(int argc, char *argv[]) { struct timespec now; if (clock_gettime(CLOCK_REALTIME, &now) == -1) handle_error("clock_gettime"); //设置超时时间 struct itimerspec new_value; new_value.it_value.tv_sec = now.tv_sec + atoi(argv[1]); new_value.it_value.tv_nsec = now.tv_nsec; new_value.it_interval.tv_sec = atoi(argv[2]); new_value.it_interval.tv_nsec = 0; int fd = timerfd_create(CLOCK_REALTIME, 0); if (fd == -1) handle_error("timerfd_create"); if (timerfd_settime(fd, TFD_TIMER_ABSTIME, &new_value, NULL) == -1) handle_error("timerfd_settime"); printTime(); printf("timer started\n"); for (uint64_t tot_exp = 0; tot_exp < atoi(argv[3]);) { uint64_t exp; //阻塞等待定时器到期。返回值是未处理的到期次数。比如定时间隔为2秒,但过了10秒才去读取,则读取的值是5 ssize_t s = read(fd, &exp, sizeof(uint64_t)); if (s != sizeof(uint64_t)) handle_error("read"); tot_exp += exp; printTime(); printf("read: %llu; total=%llu\n",exp, tot_exp); } exit(EXIT_SUCCESS); }
编译运行:编译时要加rt库(g++ -lrt timerfd.cc -o timerfd)
[root@localhost appTest]# ./timerfd 5 2 10
http://blog.csdn.net/gdutliuyun827/article/details/8470777
http://blog.csdn.net/chgaowei/article/details/21295811
7.8 定时器
Timer类
:它用来表示一个定时器,封装了定时器的超时时间(首次超时时间),间隔时间(后续周期性超时时间),回调函数等。TimerQueue类
:它表示一个定时器集合,包含当前所有定时器的集合,堆顶的超时时间最小。现在linux 内核中的定时器也可以使用文件描述符来表示了,但是对于堆中所有的定时器不能每个都开一个fd,这显然会造成fd的不够用,因此muduo的实现办法是用定时器堆顶的定时器来初始化timer_fd(只有一个timer_fd),当epoll监听到该timerfd对应的channel出现可读事件时,表示定时器到期,此时的处理办法就更巧妙了,因为可能存在超时时间和处理时间的误差,因此在处理这个定时器到期事件的同时,会查看当前定时器堆是否还有到期的定时器,对它们一并进行处理。
class Timer : boost::noncopyable { public: private: const TimerCallback callback_;//定时器回调函数 Timestamp expiration_;//超时时间 const double interval_;//时间间隔 const bool repeat_;//周期个数 };
弄清楚最小堆
class ThreadPool : boost::noncopyable { public: typedef boost::function<void ()> Task; explicit ThreadPool(const string& name = string()); ~ThreadPool(); //启动线程池 void start(int numThreads); //关闭线程池 void stop(); //运行任务,往线程池当中的任务队列添加任务 void run(const Task& f); private: //线程池当中的线程要执行的函数 void runInThread(); //获取任务 Task take(); MutexLock mutex_;//和条件变量配合使用的互斥锁 Condition cond_;//条件变量用来唤醒线程池中的线程队列来执行任务 string name_;//线程池名称 boost::ptr_vector<muduo::Thread> threads_;//存放线程指针 std::deque<Task> queue_;//任务队列 bool running_;//线程池是否处于运行的状态 };
线程池实现文件ThreadPool.cc
//启动固定的线程池 void ThreadPool::start(int numThreads) { assert(threads_.empty());//断言当前线程池为空 running_ = true;//置线程池处于运行的状态 threads_.reserve(numThreads);//预留这么多个空间 for (int i = 0; i < numThreads; ++i) {//for循环创建线程 char id[32]; //线程号 snprintf(id, sizeof id, "%d", i); //创建线程并存放线程指针,绑定的函数为runInThread threads_.push_back(new muduo::Thread(boost::bind(&ThreadPool::runInThread, this), name_+id)); threads_[i].start();//启动线程,即runInThread函数执行 } } //关闭线程池 void ThreadPool::stop() { { MutexLockGuard lock(mutex_); running_ = false;//running置为false cond_.notifyAll();//通知所有线程 } //等待线程退出 for_each(threads_.begin(),threads_.end(),boost::bind(&muduo::Thread::join, _1)); } //添加任务 void ThreadPool::run(const Task& task) {//将任务添加到线程池当中的任务队列 if (threads_.empty())//如果线程池当中的线程是空的 { task();//直接执行任务===>我的理解:event loop所在线程或线程池中的线程都有可能往任务队列中添加任务 } else//否则添加 { MutexLockGuard lock(mutex_); queue_.push_back(task); cond_.notify();//通知队列当中有任务了 } } //获取任务函数 ThreadPool::Task ThreadPool::take() {//加锁保护 MutexLockGuard lock(mutex_); // always use a while-loop, due to spurious wakeup //如果队列为空并且处于运行的状态 while (queue_.empty() && running_) { cond_.wait();//等待 } Task task;//定义任务变量,Task是一个函数类型 if(!queue_.empty())//有任务到来 { task = queue_.front();//取出任务 queue_.pop_front();//弹出任务 } return task;//返回任务 }
http://blog.csdn.net/L979951191/article/details/48089523
int epollfd_; //epoll句柄,以后要监听什么事件注册到这里 typedef std::vector<struct epoll_event> EventList; EventList events_; //监听到的活动的事件,作为epoll_wait参数 typedef std::map<int, Channel*> ChannelMap; ChannelMap channels_; //描述符与Channel(事件)的映射,这里的事件,不一定是正在监听的事件(因为有可能是之前监听但后来删掉不过仍留在ChannelMap中的映射) EventLoop* ownerLoop_;//指向EventLoop的指针。
对文件描述符的封装:
类的成员变量有fd以及对应的callback,该类可以设置fd关心的io事件类型,事件循环中调用epoll返回就绪的fd以及当前活动事件类型,根据当前活动事件类型调用相应的callback.每个fd对象只由一个事件循环eventloop处理