车载消息中间件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();
}
流程图
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::mutex> lock(mutex_);
// 设置下一次触发时间
next_trigger_time_ = current_time + interval_microsec_;
}
else if (expected == StateCode::INACTIVE)
{
//如果是INACTIVE,把下次触发时间设置成cancel_time
std::lock_guard<std::mutex> lock(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::mutex> lock(mutex_);
next_trigger_time_ = current_time + interval_microsec_;
return;
}
}
}
std::lock_guard<std::mutex> lock(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