C++ 全栈知识体系C++ 全栈知识体系
✿导航
  • 基础
  • 函数
  • 知识点
  • IO框架
  • 新版本特性
  • 数据库原理
  • SQL语言
  • SQL - MySQL
  • NoSQL - Redis
  • NoSQL - ElasticSearch
  • 算法基础
  • 常见算法
  • 领域算法
  • 分布式算法
  • 数据结构与算法
  • 计算机网络
  • 操作系统
  • 计算机组成
  • 开发
  • 测试
  • 架构基础
  • 分布式系统
  • 微服务
  • 中间件
  • 概念
  • 理论
  • 架构设计原则
  • 设计模式
  • 协议
  • 技术选型
  • 编码规范
  • 流水线构建 - CI/CD
  • 知识点 - Linux
  • 网站 - Nginx
  • 容器化 - Docker
  • 容器编排 - Kubernetes
  • 服务网格 - Service Mesh Istio
  • 常用快捷键 - Shortcut
  • 工具使用 - Tools
  • 开源项目
  • 学习项目
  • 个人项目
  • 项目开发
  • 项目Idea
  • 并发
  • 部署
  • 分布式
  • 知识
  • 问题
  • 编程语言与技术
  • 系统与架构
  • 软件开发实践
  • 数据处理与应用设计
  • 个人
  • 产品
  • 团队
  • 知识体系
  • Vue
关于
✿导航
  • 基础
  • 函数
  • 知识点
  • IO框架
  • 新版本特性
  • 数据库原理
  • SQL语言
  • SQL - MySQL
  • NoSQL - Redis
  • NoSQL - ElasticSearch
  • 算法基础
  • 常见算法
  • 领域算法
  • 分布式算法
  • 数据结构与算法
  • 计算机网络
  • 操作系统
  • 计算机组成
  • 开发
  • 测试
  • 架构基础
  • 分布式系统
  • 微服务
  • 中间件
  • 概念
  • 理论
  • 架构设计原则
  • 设计模式
  • 协议
  • 技术选型
  • 编码规范
  • 流水线构建 - CI/CD
  • 知识点 - Linux
  • 网站 - Nginx
  • 容器化 - Docker
  • 容器编排 - Kubernetes
  • 服务网格 - Service Mesh Istio
  • 常用快捷键 - Shortcut
  • 工具使用 - Tools
  • 开源项目
  • 学习项目
  • 个人项目
  • 项目开发
  • 项目Idea
  • 并发
  • 部署
  • 分布式
  • 知识
  • 问题
  • 编程语言与技术
  • 系统与架构
  • 软件开发实践
  • 数据处理与应用设计
  • 个人
  • 产品
  • 团队
  • 知识体系
  • Vue
关于
  • 编程语言与技术

    • Effective C++: 改善程序与设计的55个具体做法

      • 第2章 - 构造/析构/赋值运算(一)
      • 第2章 - 构造/析构/赋值运算(二)
      • 第2章 - 构造/析构/赋值运算(三)
      • 第3章 - 资源管理
      • 第4章 - 设计与声明(一)
      • 第4章 - 设计与声明(二)
      • 第5章 - 实现(一)
      • 第5章 - 实现(二)
      • 第6章 - 继承与面向对象设计
      • 第7章 - 模板与泛型编程
    • 深度探索C++对象模型

      • 第1章 - 关于对象
      • 第2章 - 构造函数语意学
      • 第3章 - Data 语意学
    • STL源码剖析

      • 第1章 - STL概论和版本简介
      • 第2章 - 空间配置器
      • 第3章 - 迭代器(iterators)概念与traits编程技法(一)
      • 第3章 - 迭代器(iterators)概念与traits编程技法(二)
      • 第4章 - 序列式容器 vector
      • 第4章 - 序列式容器 list
      • 第4章 - 序列式容器 deque
      • 第4章 - 序列式容器 stack和queue
      • 第4章 - 序列式容器 heap
      • 第4章 - 序列式容器 priority_queue
      • 第4章 - 序列式容器 slist
      • 第5章 - 关联式容器 RB-tree
      • 第5章 - 关联式容器 set和map
      • 第5章 - 关联式容器 hashtable
      • 第6章 - 算法
      • 第6章 - 算法之set
      • 第7章 - 仿函数
      • 第8章 - 配接器
  • 系统与架构

    • 深入理解计算机系统

      • 第1章 - 计算机系统漫游
      • 第2章 - 信息的表示和处理
      • 第3章 - 程序的机器级表示
      • 第5章 - 优化程序性能
      • 第6章 - 存储器层次结构
      • 第7章 - 链接
      • 第8章 - 异常控制流
      • 第9章 - 虚拟内存
      • 第10章 - 系统级I/O
      • 第11章 - 网络编程
      • 第12章 - 并发编程
    • 大型网站技术架构——核心原理与案例分析

      • 第1章 - 大型网站架构演化
      • 第2章 - 大型网站架构模式
      • 第3章 - 大型网站核心架构要素
      • 第4章 - 瞬时响应:网站的高性能架构
      • 第5章 - 万无一失:网站的高可用架构
      • 第6章 - 永无止境:网站的伸缩性架构
      • 第7章 - 随需应变:网站的可扩展架构
      • 第8章 - 固若金汤:网站的安全架构
    • 从零开始学架构

      • 架构基础
      • 架构设计原则
      • 高性能架构
      • 高可用架构
    • 程序员的自我修养————链接、装载与库

      • 第1章 - 简介
      • 第2章 - 静态链路
      • 第3章 - 目标文件里有什么
      • 第4章 - 静态链接
      • 第7章 - 动态链接
      • 第8章 - 共享库版本
      • 第10章 - 内存
      • 第11章 - 运行库
      • 第12章 - 系统调用与API
      • 第13章 - 运行库实现
  • 软件开发实践

    • 重构改善既有代码的设计

      • 第1章 - 重构,第一个示例
      • 第2章 - 重构的原则
      • 第3章 - 代码的坏味道
      • 第5章 - 重构列表
      • 第6章 - 重新组织函数
      • 第7章 - 在对象之间搬移特性
      • 第8章 - 重新组织数据
      • 第9章 - 简化条件表达式
      • 第10章 - 简化函数调用
      • 第11章 - 处理概括关系
      • 第12章 - 设计之大型重构
    • 代码大全2

      • 第1章 - 欢迎进入软件构建的世界
      • 第2章 - 用隐喻来更充分地理解软件开发
      • 第3章 - 三思而后行: 前期准备
      • 第4章 - 关键的构建决策
      • 第5章 - 软件构建中的设计
    • Linux多线程服务端编程——使用muduo C++ 网络库

      • Buffer类的设计
      • 设计与实现
      • 定时器与TimerQueue
      • Protobuf网络传输和Protobuf编解码器与消息分发器
      • EventLoop类剖析
      • EventLoopThread和EventLoopThreadPool剖析
      • TCP网络库和核心类
      • Connector剖析
      • TcpClient剖析
      • 学习总结
      • timing wheel
      • 消息广播服务
      • 线程安全的对象生命期管理
  • 数据处理与应用设计

    • 数据密集型应用系统设计

      • 第1章 - 可靠、可扩展与可维护的应用系统
      • 第2章 - 数据模型与查询语言
      • 第3章 - 数据存储与检索
      • 第4章 - 数据编码与演化
      • 第5章 - 数据复制
      • 第6章 - 数据分区
      • 第7章 - 事务

muduo - 定时器与TimerQueue

    muduo库时间函数的选择

    • 计时:只使用 gettimeofday 来获取当前时间。gettimeofday 的分辨率 (resolution) 是 1 微秒,足以满足日常计时的需要。muduo::Timestamp 用一个 int64_t 来表示从 Epoch 到现在的微秒数,其范围可达上下 30 万年。

    • 定时:只使用 timerfd_* 系列函数来处理定时。(timerfd_create / timerfd_gettime / timerfd_settime)timerfd_create 把时间变成了一个文件描述符,该“文件”在定时器超时的那一刻变得可读,这样就能很方便地融入到 select/poll 框架中,用统一的方式来处理 IO 事件和超时事件,这也正是 Reactor 模式的长处。

    Timer* 类的设计与实现

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

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

    • TimerQueue其实现时基于Set的。TimerQueue的封装是为了让未到期的时间Timer有序的排列起来,这样,能够根据当前时间找到已经到期的Timer也能高效的添加和删除Timer。内部有channel,和timerfd关联。添加新的Timer后,在超时后,timerfd可读,会处理channel事件,之后调用Timer的回调函数;在timerfd的事件处理后,还有检查一遍超时定时器,如果其属性为重复还有再次添加到定时器集合中。

    这里涉及了3个类TimerId、Timer、TimerQueue要知道他们怎么与EventLoop结合起来,还是从一个示例开始。简单来说,TimerQueue是用来进行管理调度的,而Timer是真正的超时事件(该Class中封装了真正的超时回调)。

    void printTid()
    {
      printf("pid = %d, tid = %d\n", getpid(), muduo::CurrentThread::tid());
      printf("now %s\n", muduo::Timestamp::now().toString().c_str());
    }
    
    void print(const char* msg)
    {
      printf("msg %s %s\n", muduo::Timestamp::now().toString().c_str(), msg);
      if (++cnt == 20)
      {
        g_loop->quit();
      }
    }
    
    int main()
    {
      printTid();
      muduo::EventLoop loop;
      g_loop = &loop;
    
      print("main");
      loop.runAfter(1, boost::bind(print, "once1"));
    #if 1
      loop.runAfter(1.5, boost::bind(print, "once1.5"));
      loop.runAfter(2.5, boost::bind(print, "once2.5"));
      loop.runAfter(3.5, boost::bind(print, "once3.5"));
      loop.runEvery(2, boost::bind(print, "every2"));
      loop.runEvery(3, boost::bind(print, "every3"));
    #endif
      loop.loop();
      print("main loop exits");
      sleep(1);
    }
    

    首先,是EventLoop类对象的初始化:

    EventLoop::EventLoop()
      : looping_(false),
        quit_(false),
        eventHandling_(false),
        callingPendingFunctors_(false),
        iteration_(0),
        threadId_(CurrentThread::tid()),
        poller_(Poller::newDefaultPoller(this)),
        timerQueue_(new TimerQueue(this)),
        wakeupFd_(createEventfd()),
        wakeupChannel_(new Channel(this, wakeupFd_)),
        currentActiveChannel_(NULL)
    {
        ...
    }
    

    这里,我们看到在EventLoop的构造函数中对Poller和TimerQueue类型的成员进行初始化。然后,看TimerQueue的构造函数:

    TimerQueue::TimerQueue(EventLoop* loop)
      : loop_(loop),
        timerfd_(createTimerfd()), //timefd描述符
        timerfdChannel_(loop, timerfd_), //channel对象
        timers_(),    //保存Timer的关键结构
        callingExpiredTimers_(false)
    {
      timerfdChannel_.setReadCallback(
          std::bind(&TimerQueue::handleRead, this));
      // we are always reading the timerfd, we disarm it with timerfd_settime.
      timerfdChannel_.enableReading();
    }
    

    将timefd和Channel关联起来,并绑定TimerQueue::handleRead作为描述符可读时的回调函数,而在TimerQueue::handleRead中又会去调用超时的Timer的回调。

    void TimerQueue::handleRead()
    {
      loop_->assertInLoopThread();
      Timestamp now(Timestamp::now());
      readTimerfd(timerfd_, now);
    
      std::vector<Entry> expired = getExpired(now);//关键处理逻辑
    
      callingExpiredTimers_ = true;
      cancelingTimers_.clear();
      // safe to callback outside critical section
      for (const Entry& it : expired)
      {
        it.second->run(); //Timer的回调
      }
      callingExpiredTimers_ = false;
    
      reset(expired, now);
    }
    

    其中

    Timer(TimerCallback cb, Timestamp when, double interval)
        : callback_(std::move(cb)),
          expiration_(when),
          interval_(interval),
          repeat_(interval > 0.0),
          sequence_(s_numCreated_.incrementAndGet())
      { }
    
      void run() const
      {
        callback_();
      }
    

    TimerQueue的关键处理函数getExpired的实现,它会从timers_中移除已到期的Timer,同时更新activeTimers_(主要在cancelInLoop中使用,取消活动的定时器),并通过vector返回超期的Timer。

    typedef std::pair<Timestamp, Timer*> Entry;
    typedef std::set<Entry> TimerList;
    typedef std::pair<Timer*, int64_t> ActiveTimer;
    typedef std::set<ActiveTimer> ActiveTimerSet;
      
    TimerList timers_;
    ActiveTimerSet activeTimers_; // for cancel()
    //TimerQueue的结构
    
    std::vector<TimerQueue::Entry> TimerQueue::getExpired(Timestamp now)
    {
      assert(timers_.size() == activeTimers_.size());
      std::vector<Entry> expired;
      Entry sentry(now, reinterpret_cast<Timer*>(UINTPTR_MAX));
      TimerList::iterator end = timers_.lower_bound(sentry);//返回第一个大于等于now的迭代器,小于now的都已经超时
      assert(end == timers_.end() || now < end->first);
      std::copy(timers_.begin(), end, back_inserter(expired));//[begin end)之间的元素追加到expired末尾
      timers_.erase(timers_.begin(), end);//删除超时定时器
    
      //for (const Entry& it : expired)
      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);//删除超时定时器,即同步timers_和activeTimers_
        assert(n == 1); (void)n;
      }
    
      assert(timers_.size() == activeTimers_.size());
      return expired;
    }
    

    构造函数帮我们做了很多事情,不过上述情况是假设TimerQueue中已经有一系列已经排好序列的时间事件了,现在要来看看怎么添加这些时间。对应EventLoop中的函数就是runAt、runAfter、runEvery,这三个函数其实底层调用的TimeQueue::addTimer函数,只是传参不同而已。addTimer函数只负责转发,addTimerInLoop完成修改定时器列表的工作。

    TimerId EventLoop::runAt(Timestamp time, TimerCallback cb)
    {
      return timerQueue_->addTimer(std::move(cb), time, 0.0);
    }
    
    TimerId EventLoop::runAfter(double delay, TimerCallback cb)
    {
      Timestamp time(addTime(Timestamp::now(), delay));
      return runAt(time, std::move(cb));
    }
    
    TimerId EventLoop::runEvery(double interval, TimerCallback cb)
    {
      Timestamp time(addTime(Timestamp::now(), interval));
      return timerQueue_->addTimer(std::move(cb), time, interval);
    }
    
    TimerId TimerQueue::addTimer(TimerCallback cb,
                                 Timestamp when,
                                 double interval)
    {
      Timer* timer = new Timer(std::move(cb), when, interval);
      loop_->runInLoop(
          std::bind(&TimerQueue::addTimerInLoop, this, timer));
      return TimerId(timer, timer->sequence());
    }
    
    void TimerQueue::addTimerInLoop(Timer* timer)
    {
      loop_->assertInLoopThread();
      bool earliestChanged = insert(timer);
    
      if (earliestChanged)
      {
        resetTimerfd(timerfd_, timer->expiration());
      }
    }
    

    整个处理过程如下:

    1. EventLoop的构造函数创建TimerQueue的对象,TimerQueue构造函数中创建timerfd,并通过timerfdChannel_绑定timerfd和loop,设置超时回调该回调将用来调用Timer的callback_。
    2. EventLoop通过runAt、runAfter、runEvery添加Timer和设置Timer的回调,这三个函数是通过设置不同的参数去调用TimeQueue::addTimer。
    3. 此时定时器已经启动,当发生超时事件时,timerfd可读,poll调用返回。
    4. 通过activeChannl返回活跃事件(这里的活跃事件即为timerfd可读事件)
    5. 在Channel::handleRead()回调中将执行在TimerQueue构造函数中绑定的TimerQueue::handleRead()。
    6. TimerQueue::handleRead()又将获取所有超时的Timer并执行Timer中的回调。

    TimerQueue回调用户代码onTime的时序图:

    ​TimerQueue时序图

    Last Updated:
    Contributors: klc407073648
    Prev
    设计与实现
    Next
    Protobuf网络传输和Protobuf编解码器与消息分发器