• muduo定时器、多线程模型及epoll的封装


    timerfd是Linux为用户程序提供的一个定时器接口,这个接口基于文件描述符。

    clock_gettime函数可以获取系统时钟,精确到纳秒。需要在编译时指定库:-lrt。可以获取两种类型时间:

    CLOCK_REALTIME:相对时间,从1970.1.1到目前的时间。更改系统时间会更改获取的值。也就是,它以系统时间为坐标。
    CLOCK_MONOTONIC:与CLOCK_REALTIME相反,它是以绝对时间为准,获取的时间为系统重启到现在的时间,更改系统时间对齐没有影响。

    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

    it_interval不为0则表示是周期性定时器,it_value和it_interval都为0表示停止定时器。
    #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
    ");
       
       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
    ",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  定时器
    在一般的服务端程序设计中,与时间有关的常见任务有:
    1.获取当前时间,计算时间间隔===>使用gettimeofday来获取当前时间,时间精度是1微秒
    2.定时操作===>使用timerfd来处理定时任务,把时间变成一个文件描述符,可以通过epoll进行管理,用统一的方式来处理IO事件和超时事件
    通过最小堆来管理未到期的timers,所有操作的时间复杂度为O(logN) 
    • Timer类:它用来表示一个定时器,封装了定时器的超时时间(首次超时时间),间隔时间(后续周期性超时时间),回调函数等。
    • TimerQueue类:它表示一个定时器集合,包含当前所有定时器的集合,堆顶的超时时间最小。现在linux 内核中的定时器也可以使用文件描述符来表示了,但是对于堆中所有的定时器不能每个都开一个fd,这显然会造成fd的不够用,因此muduo的实现办法是用定时器堆顶的定时器来初始化timer_fd(只有一个timer_fd),当epoll监听到该timerfd对应的channel出现可读事件时,表示定时器到期,此时的处理办法就更巧妙了,因为可能存在超时时间和处理时间的误差,因此在处理这个定时器到期事件的同时,会查看当前定时器堆是否还有到期的定时器,对它们一并进行处理。
    Timer类:
    class Timer : boost::noncopyable
    {
     public:
    
     private:
      const TimerCallback callback_;//定时器回调函数
      Timestamp expiration_;//超时时间
      const double interval_;//时间间隔
      const bool repeat_;//周期个数
    };

    弄清楚最小堆

     
     
    https://www.zhihu.com/question/32251997
    http://coderfirefly.com/2015/09/17/150917_muduo_6/
    https://www.ibm.com/developerworks/cn/linux/l-cn-timers/
    服务器端采用非阻塞IO+IO多路复用程序中有多个event loop(事件循环),每个线程至多有一个event loop,其中event loop用作IO多路复用,配合非阻塞IO和定时器使用.线程队列中的线程不用处理IO只需要执行计算任务。
    muduo库里面的线程池是固定线程池,即创建的线程池里面的线程个数是一定的,不是动态的。线程池里面包含线程队列还有任务队列,外部程序将任务存放到线程池的任务队列中,线程池中的线程队列执行任务,是一种生产者和消费者模型(condition_variable、unique_lock)
    线程队列:boost::ptr_vector<muduo::Thread> threads_
    任务队列:std::deque<Task> queue_
    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


    EPollPoller(epoll封装类)类对象监听3种类型的事件,一种是由Channel类描述的读写事件,一种是TimeQueue类描述的定时事件(然而也是通过Channel来注册到poller上),还有一种则是自身线程唤醒事件(eventfd)。 
    成员变量:
      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处理

     
     
     
     
     
     
     
    http://www.voidcn.com/blog/u010235142/article/p-6071685.html
     
     
     
     
     
     
     
     
     
     
     
     
  • 相关阅读:
    面向对象的设计模式2
    数据结构
    算法题目1
    5.7(1) 反射
    hashMap原理(java8)
    6.1 接口
    18.1 线程
    13.2 具体的集合
    scrapy(2)——scrapy爬取新浪微博(单机版)
    5.1 类、超类和子类
  • 原文地址:https://www.cnblogs.com/ljygoodgoodstudydaydayup/p/5710984.html
Copyright © 2020-2023  润新知