车载消息中间件FastDDS 源码解析(八)TimedEvent

471 阅读8分钟

车载消息中间件FastDDS 源码解析(一)FastDDS 介绍和使用

车载消息中间件FastDDS 源码解析(二)RtpsParticipant的创建(上)

车载消息中间件FastDDS 源码解析(三)RtpsParticipant的创建(中)

车载消息中间件FastDDS 源码解析(四)RtpsParticipant的创建(下)

车载消息中间件FastDDS 源码解析(五)BuiltinProtocols(上)

车载消息中间件FastDDS 源码解析(六)BuiltinProtocols(中)EDP

车载消息中间件FastDDS 源码解析(七)BuiltinProtocols(下)WLP&TypeLookupManager

这篇介绍一下 TimedEvent 我们在写代码的过程中都会有一些周期性的事件需要处理,在android中类似的有handler。在fastdds中就是自己创建了一个TimedEvent来处理这些周期性的事件。

1. TimedEvent 简介

fastdds 经常需要产生一些周期性的事件,比如发送心跳,发送discovery 消息等等,fastdds 用两个类统一处理了这些周期性的事件TimedEvent 和 ResourceEvent。

ResourceEvent是TimedEvent的管理类,他里面有个线程在后台不停处理TimedEvent的事件。 ResourceEvent在两个地方初始化,一在RTPSParticipantImpl 初始化的时候init_thread。 见车载消息中间件FastDDS 源码解析(三)RtpsParticipant的创建(中) 第4部分 另一个地方在PDPServer 处初始化,初始化完成之后就一直在后台轮询,如果有TimedEvent 到了触发时间,就做相应的处理。

1.1 TimedEvent的使用

我们以xxx_event_为例来说一下 TimedEvent的使用, 这个TimedEvent主要负责周期性的事件

1.1.1初始化

 xxx_event_ = new TimedEvent(
         pimpl->getEventResource(),
        [&]() -> bool
        {
             return xxx();
        },
         TimeConv::Time_t2MilliSecondsDouble(m_times.heartbeatPeriod));

这儿有三个参数:

1.pimpl->getEventResource(),这个是我们上面说到的ResourceEvent,在RTPSParticipantImpl中的一个属性,在RTPSParticipantImpl初始化的时候被初始化,本质是一个线程,不断的轮训处理TimedEvent事件。

2.一个回调函数,就是要执行的函数,在达到触发条件的时候,被触发。

3.时间周期,就是这个TimedEvent多长时间被触发一次

1.1.2更新时间周期

 xxx_event_->update_interval(times.heartbeatPeriod)

当希望改动周期时间的时候,就使用这个函数来改变周期。

1.1.3重置时间

 xxx_event_->restart_timer()

这个函数是启动这个TimedEvent,将这个TimedEvent 加入队列

1.1.4取消周期事件

 xxx_event_->cancel_timer();

表示这个周期事件不再执行

1.2TimedEvent的源码解析

1.2.1时序图

这个时序图,主要是表示TimedEvent 初始化和启动的场景

sequenceDiagram
participant User
		participant TimedEvent
		participant TimedEventImpl
		participant ResourceEvent
		
		User ->> TimedEvent:1.new
		TimedEvent ->> TimedEventImpl:2.new
		TimedEvent ->> ResourceEvent:3.register_timer
		User ->> TimedEvent:4.restart_timer
		TimedEvent ->> TimedEventImpl:5.go_ready
		TimedEvent ->> ResourceEvent:6.notify
		ResourceEvent ->> ResourceEvent:7.register_timer_nts

TimedEvent的使用一般分为2步:见1和4

1.TimedEvent 初始化,传三个参数,一个是ResourceEvent,一个 callback函数,一个是milliseconds

ResourceEvent 在之前有过介绍主要负责管理TimedEvent,callback函数是TimedEvent被触发时,调用的函数,最后一个参数表示的时间间隔

初始化的时候 主要干了2件事

a.new 了一个 TimedEventImpl 见2

b.调用了ResourceEvent 的register_timer 见3

2.TimedEventImpl 看名字就知道,TimedEvent的实现类

3.ResourceEvent 的register_timer 这里面只是做了一些参数的处理

4.TimedEvent 的 restart_timer函数 主要做了这两件事情:

a.调用TimedEventImpl::go_ready函数,将TimedEventImpl设置成ready的状态 见5

b.ResourceEvent::notify 见6

5.TimedEventImpl::go_ready函数主要是设置状态

TimedEventImpl 主要有3种状态INACTIVE, READY, WAITING

看函数名字的意思就是 从INACTIVE转到READY状态

然后调用ResourceEvent的notify函数

6.ResourceEvent的notify函数 主要调用了ResourceEvent的register_timer_nts 函数 见7

7.ResourceEvent的register_timer_nts主要是将TimedEventImpl 放入了pending_timers_ 中

1.2.2TimeEvent 初始化

步骤1:

 TimedEvent::TimedEvent(
         ResourceEvent& service,
         std::function<bool()> callback,
         double milliseconds)
    : service_(service)
    , impl_(nullptr)
 {
     impl_ = new TimedEventImpl(
         callback,
         std::chrono::microseconds(static_cast<int64_t>(milliseconds * 1000)));
     service_.register_timer(impl_);
 }

这儿有三个参数:

1.pimpl->getEventResource(),这个是我们上面说到的ResourceEvent,在RTPSParticipantImpl中的一个属性,在RTPSParticipantImpl初始化的时候被初始化,本质是一个线程,不断的轮训处理TimedEvent事件。

2.一个回调函数,就是发送心跳的函数,在达到触发条件的时候,被触发。

3.时间周期,就是这个TimedEvent多长时间被触发一次

主要干了2件事

1.创建了一个Timed EventImpl对象,对应时序图步骤2

这个函数没有什么太多内容,这边就不看了

2.调用了ResourceEvent::register_timer

步骤3

 void ResourceEvent::register_timer(
         TimedEventImpl* /*event*/)
 {
    {
         std::lock_guard<TimedMutex> lock(mutex_);
         ++timers_count_;
    }
 ​
     // Notify the execution thread that something changed
     cv_.notify_one();
 }

这个是多线程的处理

2.2.3restart_timer

步骤4:

 void TimedEvent::restart_timer()
 {
     if (impl_->go_ready())
    {
         service_.notify(impl_);
    }
 }
 ​

1.impl_->go_ready() 把状态改成StateCode::READY

2.service .notify(impl) 主要是把impl放入一个队列中

步骤5:

 bool TimedEventImpl::go_ready()
 {
     bool returned_value = false;
     StateCode expected = StateCode::INACTIVE;
 ​
     if (state_.compare_exchange_strong(expected, StateCode::READY))
    {
         returned_value = true;
    }
 ​
     return returned_value;
 }

主要是把state_状态改成StateCode::READY

这里面使用到了c++原子锁保证了线程安全

步骤6:

 void ResourceEvent::notify(
         TimedEventImpl* event,
         const std::chrono::steady_clock::time_point& timeout)
 {
 #if HAVE_STRICT_REALTIME
     std::unique_lock<TimedMutex> lock(mutex_, std::defer_lock);
     if (lock.try_lock_until(timeout))
 #else
     static_cast<void>(timeout);
     std::lock_guard<TimedMutex> _(mutex_);
 #endif  // HAVE_STRICT_REALTIME
    {
         if (register_timer_nts(event))
        {
             // Notify the execution thread that something changed
             cv_.notify_one();
        }
    }
 }

主要是调用了register_timer_nts

步骤7:

 bool ResourceEvent::register_timer_nts(
         TimedEventImpl* event)
 {
     if (std::find(pending_timers_.begin(), pending_timers_.end(), event) == pending_timers_.end())
    {
         pending_timers_.push_back(event);
         return true;
    }
 ​
     return false;
 }
 ​

将TimedEvent放入到等待队列pending_timers_中去。

3 ResourceEvent源码解析

3.1ResourceEvent初始化

 void ResourceEvent::init_thread()
 {
     std::lock_guard<TimedMutex> lock(mutex_);
 ​
     allow_vector_manipulation_ = false;
     stop_.store(false);
     resize_collections();
 ​
     thread_ = std::thread(&ResourceEvent::event_service, this);
 }

创建了一个线程,这个线程的运行函数是event_service

3.2ResourceEvent 主循环

 void ResourceEvent::event_service()
 {
     while (!stop_.load())
    {
         // Perform update and execution of timers
         update_current_time();
         do_timer_actions();
 ​
         std::unique_lock<TimedMutex> lock(mutex_);
 ​
         // If the thread has already been instructed to stop, do it.
         if (stop_.load())
        {
             break;
        }
 ​
         // If pending timers exist, there is some work to be done, so no need to wait.
         // 不为空,就跳过剩余的代码,先处理pending_timers
         if (!pending_timers_.empty())
        {
             continue;
        }
 ​
         // Allow other threads to manipulate the timer collections while we wait.
         allow_vector_manipulation_ = true;
         cv_manipulation_.notify_all();
 ​
         // Wait for the first timer to be triggered
         std::chrono::steady_clock::time_point next_trigger =
                 active_timers_.empty() ?
                 current_time_ + std::chrono::seconds(1) :
                 active_timers_[0]->next_trigger_time();
 ​
         auto current_time = std::chrono::steady_clock::now();
         if (current_time > next_trigger)
        {
             next_trigger = current_time + std::chrono::microseconds(10);
        }
         //等10s 或者等到next_trigger时间
         cv_.wait_until(lock, next_trigger);
 ​
         // Don't allow other threads to manipulate the timer collections
         allow_vector_manipulation_ = false;
         resize_collections();
    }
 ​
     // Thread being stopped. Allow other threads to manipulate the timer collections.
    {
         std::lock_guard<TimedMutex> guard(mutex_);
         allow_vector_manipulation_ = true;
    }
     cv_manipulation_.notify_all();
 }

流程图

image.png ResourceEvent 的循环函数event_service 主要是调用了do_timer_actions

检查 stop_是否为true,为true 跳出循环退出,否则调用do_timer_actions

 void ResourceEvent::do_timer_actions()
 {
     std::chrono::steady_clock::time_point cancel_time =
             current_time_ + std::chrono::hours(24);
 ​
     bool did_something = false;
 ​
     // Process pending orders
    {
         std::lock_guard<TimedMutex> lock(mutex_);
       // 遍历 pending_timers_
       // 将 pending_timers_中数据更新信息,然后插入active_timers_
         for (TimedEventImpl* tp : pending_timers_)
        {
             // Remove item from active timers
           // 
             auto current_pos = std::lower_bound(active_timers_.begin(), active_timers_.end(), tp, event_compare);
             current_pos = std::find(current_pos, active_timers_.end(), tp);
             if (current_pos != active_timers_.end())
            {
                 active_timers_.erase(current_pos);
            }
 ​
             // Update timer info
             // tp 的状态从 READY 改成StateCode::WAITING,同时设置下次的触发事件
             if (tp->update(current_time_, cancel_time))
            {
                 // Timer has to be activated: add to active timers
                 std::vector<TimedEventImpl*>::iterator low_bound;
 ​
                 // Insert on correct position
                 low_bound = std::lower_bound(active_timers_.begin(), active_timers_.end(), tp, event_compare);
                 active_timers_.emplace(low_bound, tp);
            }
        }
         pending_timers_.clear();
    }
 ​
     // Trigger active timers
     skip_checking_active_timers_.store(false);
     for (TimedEventImpl* tp : active_timers_)
    {
       // 如果TimedEvent 到了被触发的时机
         if (tp->next_trigger_time() <= current_time_)
        {
             did_something = true;
           
             tp->trigger(current_time_, cancel_time);
 ​
             //! skip this iteration as active_timers has been manipulated
             if (skip_checking_active_timers_.load())
            {
                 break;
            }
        }
         else
        {
             break;
        }
    }
 ​
     // If an action was made, keep active_timers_ sorted
     if (did_something)
    {
       //重排
         sort_timers();
       //删除到cancel_time的TimedEvent
         active_timers_.erase(
             std::lower_bound(active_timers_.begin(), active_timers_.end(), nullptr,
            [cancel_time](
                 TimedEventImpl* a,
                 TimedEventImpl* b)
            {
                (void)b;
                 return a->next_trigger_time() < cancel_time;
            }),
             active_timers_.end()
            );
    }
 }

主要干了这几件事

1.将pending_timers中的TimedEventImpl 的update 之后,全部放入active_timers 中去,

2.检测active_timers中的TimedEventImpl的触发时间,如果到触发时间,就调用触发函数TimedEventImpl::trigger,如此循环往复

3.对active_timers中的TimedEventImpl进行整理,按照时间排序,删除cancel_time的TimedEvent

 bool TimedEventImpl::update(
         std::chrono::steady_clock::time_point current_time,
         std::chrono::steady_clock::time_point cancel_time)
 {
     StateCode expected = StateCode::READY;
     bool set_time = state_.compare_exchange_strong(expected, StateCode::WAITING);
 ​
     if (set_time)
    {
         std::lock_guard<std::mutexlock(mutex_);
         // 设置下一次触发时间
         next_trigger_time_ = current_time + interval_microsec_;
    }
     else if (expected == StateCode::INACTIVE)
    {
       //如果是INACTIVE,把下次触发时间设置成cancel_time
         std::lock_guard<std::mutexlock(mutex_);
         next_trigger_time_ = cancel_time;
    }
 ​
     return expected != StateCode::INACTIVE;
 }

1.主要干了两件事 将TimedEventImpl的状态从StateCode::READY 转到 StateCode::WAITING

2.设置下次触发的时间

 void TimedEventImpl::trigger(
         std::chrono::steady_clock::time_point current_time,
         std::chrono::steady_clock::time_point cancel_time)
 {
     if (callback_)
    {
         StateCode expected = StateCode::WAITING;
         // state_ 与expected StateCode::WAITING 相等,为true,state_ 被修改为StateCode::INACTIVE
         if (state_.compare_exchange_strong(expected, StateCode::INACTIVE))
        {
 ​
             //Exec
             //执行event 状态转为StateCode::WAITING
             bool restart = callback_();
 ​
             if (restart)
            {
                 expected = StateCode::INACTIVE;
                 // state_ 与expected StateCode::INACTIVE 相等,为true,state_ 被修改为StateCode::WAITING
                 if (state_.compare_exchange_strong(expected, StateCode::WAITING))
                {
                     std::lock_guard<std::mutexlock(mutex_);
                     next_trigger_time_ = current_time + interval_microsec_;
                     return;
                }
            }
        }
 ​
         std::lock_guard<std::mutexlock(mutex_);
         next_trigger_time_ = cancel_time;
    }
 }

1.将TimedEventImpl的状态从StateCode::WAITING转到StateCode::INACTIVE

2.执行callback函数,就是我们timedevent要执行的周期函数

3.如果需要重复执行,则将将TimedEventImpl的状态从StateCode::INACTIVE转到StateCode::WAITING

设置下一次执行时间

3.3TimedEvent的状态机

stateDiagram
INACTIVE --> READY:go_ready      
READY --> WAITING:update  
WAITING -->INACTIVE:trigger
INACTIVE -->WAITING:trigger

这是timedevent 的状态转换图

1.timedevent 初始状态是INAVTIVE

2.timedevent 调用go_ready函数之后转到READY状态,之后timedevent 会被放入ResourceEvent的pending_timers_中去

3.ResourceEvent 后台线程会不断循环将pending_timers中的timedevent调用timedevent的update函数,更新触发时间,将timedevent的状态转到WAITING状态。 放入active_timers中去,

4.ResourceEvent 后台线程不断循环查看active_timers中的timedevent是否到触发时间,到触发时间的调用trigger函数,将timedevent的状态转为INACTIVE,调用执行event,执行完毕后将timedevent的状态转为WAITING状态,设置下一个触发时间,等待下次触发

这一篇介绍了TimedEvent,这个在fastdds中使用比较多,发送周期性事件的时候都会使用到。

车载消息中间件FastDDS 源码解析(一)FastDDS 介绍和使用

车载消息中间件FastDDS 源码解析(二)RtpsParticipant的创建(上)

车载消息中间件FastDDS 源码解析(三)RtpsParticipant的创建(中)

车载消息中间件FastDDS 源码解析(四)RtpsParticipant的创建(下)

车载消息中间件FastDDS 源码解析(五)BuiltinProtocols(上)

车载消息中间件FastDDS 源码解析(六)BuiltinProtocols(中)EDP

车载消息中间件FastDDS 源码解析(七)BuiltinProtocols(下)WLP&TypeLookupManager