[翻译]Iceoryx - Listener

1. 概述

本文翻译自:design/listener.md
除了WaitSet之外,Listener也是实现推送方法检测和响应某些事件的构建块之一。Listener为用户提供了将对象与相应的事件和回调关联的能力。每当对象接收到指定事件时,Listener后台线程会作为反应调用相应的回调。

与WaitSet的两个关键区别在于,Listener是事件驱动的,而不是事件和状态驱动的,并且Listener创建了一个单独的后台线程来执行事件回调,而在WaitSet中用户必须显式调用事件回调。

2. 术语

  • Condition Variable
    附加对象用于通知Listener/WaitSet事件已发生。
  • event 改变对象状态的事件。
  • event driven 由事件直接引发的一次性反应。
    例如:新样本已被传递给订阅者。
  • state 预定义的对象成员设置值。
  • state driven 持续反应,只要状态持续存在。
    例如:订阅者存储了用户未检查的样本。

3. 设计

Listener是Reactor反应器模式的一种变体,其使用方法应类似于WaitSet,但有一个关键区别——它应是事件驱动的,而不是像WaitSet那样根据附加的事件混合事件和状态驱动。

3.1 需求

  • 每当事件发生时,应尽快调用相应的回调once
  • 如果在调用回调之前事件发生多次,应once调用回调。
  • 如果在执行回调时事件发生,应once再次调用回调。
  • 线程安全:可以随时从任何线程附加事件。
  • 线程安全:可以随时从任何线程分离事件。
    • 如果回调当前正在运行,detachEvent将阻塞直到回调完成。
    • detachEvent调用后,不再调用事件回调,即使事件在detachEvent运行期间被信号通知并且回调尚未执行。
    • 如果从事件回调中分离回调,则detachEvent是非阻塞的。事件在detachEvent调用后立即分离。
  • 可以为特定对象的特定事件附加最多一个回调。
    • 通常由开发人员定义的枚举。例如SubscriberEvent::DATA_RECEIVED
    • 附加到已经附加回调的事件会导致错误。
  • 可以同时为不同对象附加相同事件。
  • 可以为单个对象附加多个不同事件。
  • 当Listener超出作用域时,通过附加对象提供的回调将其从每个附加对象分离(如WaitSet中)。
  • 当附加到Listener的类超出作用域时,通过Listener提供的回调将其从Listener分离(如WaitSet中)。

3.2 解决方案

3.3.1 类图
1
1
1
n
1
1
n
1
1
n
n
1
1
n
ConditionVariableData
- m_semaphore
- m_runtimeName
- m_toBeDestroyed
- m_activeNotifications
ConditionListener
- m_condVarDataPtr : ConditionVariableData*
- m_toBeDestroyed : std::atomic_bool
ConditionListener(ConditionVariableData &)
bool wasNotified()
void destroy()
NotificationVector_t wait()
NotificationVector_t timedWait()
ConditionNotifier
- m_condVarDataPtr : ConditionVariableData*
- m_notificationIndex
ConditionNotifier(ConditionVariableData &, uint64_t notificationIndex)
void notify()
TriggerHandle
- m_conditionVariableDataPtr
- m_resetCallback
- m_uniqueTriggerId
bool isValid()
bool wasTriggered()
void trigger()
void reset()
void invalidate()
void getUniqueId()
Listener
- m_events : Event_t[]
- m_thread : std::thread
- m_conditionListener : ConditionListener
attachEvent(Triggerable, EventType, Callback)
detachEvent(Triggerable, EventType)
Event_t
- m_origin
- m_callback
- m_invalidationCallback
- m_eventId
void executeCallback()
bool reset()
bool init(...)
Triggerable
- m_triggerHandle : TriggerHandle
void invalidateTrigger(const uint64_t triggerId)
void enableEvent(TriggerHandle&&, const EventEnum)
void enableEvent(TriggerHandle&&)
void disableEvent(const EventEnum)
void disableEvent()
3.3.2 类交互
  • 创建Listener: 在共享内存中创建ConditionVariableData。Listener使用ConditionListener等待传入的事件。
                                        PoshRuntime
Listener                                    |
  |   getMiddlewareConditionVariable : var  |
  | --------------------------------------> |
  |   ConditionListener(var)                |             ConditionListener
  | ----------------------------------------+-----------------> |
  |   wait() : vector<uint64_t>             |                   |
  | ----------------------------------------+-----------------> |

  • 将Triggerable事件(SubscriberEvent::DATA_RECEIVED)附加到Listener:
    Listener创建TriggerHandle并通过enableEvent将其提供给Triggerable(Subscriber),使Triggerable拥有该句柄。每当事件发生时,Triggerable可以使用TriggerHandle的trigger()方法通知Listener。
User                Listener                                            Triggerable
 |   attachEvent()     |                                                     |
 | ------------------> |      TriggerHandle                                  |
 |                     |   create   |                                        |
 |                     | ---------> |                                        |
 |                     |        enableEvent(std::move(TriggerHandle))        |
 |                     | -----------+--------------------------------------> |

  • 从Triggerable信号事件: 调用TriggerHandle::trigger(),Listener从ConditionListener.wait()调用返回,并检索所有信号通知的列表。调用相应的事件回调。
Triggerable     TriggerHandle  ConditionNotifier    ConditionListener                Listener                     Event_t
     |    trigger()   |                |                    |   wait() : notificationIds |                           |
     | -------------> |    notify()    |                    | <------------------------- |                           |
     |                | -------------> | .... unblocks .... |            blocks          |  exeuteCallback()         |
     |                |                |                    |                            | ------------------------> |
     |                |                |                    |                            |  m_events[notificationId] |

  • Triggerable超出作用域: TriggerHandle是Triggerable的成员,因此调用TriggerHandle的析构函数,通过resetCallback从Listener中移除触发器。
Triggerable        TriggerHandle         Listener         Event_t
     |  ~TriggerHandle   |                   |               |
     | ----------------> |  removeTrigger()  |               |
     |                   | ----------------> |    reset()    |
     |                   | via resetCallback | ------------> |

  • Listener超出作用域: Event_t的析构函数通过invalidationCallback使Triggerable中的Trigger无效。
Triggerable        TriggerHandle         Listener         Event_t
     |  ~TriggerHandle   |                   |               |
     | ----------------> |  removeTrigger()  |               |
     |                   | ----------------> |    reset()    |
     |                   | via resetCallback | ------------> |

3.3.3 TriggerHandle
  • 问题: Triggerable应能够在没有任何有关这些类的知识的情况下通知Listener/WaitSet,以防止循环依赖。此外,Triggerable必须能够在超出作用域时移除其附加的事件。
  • 解决方案: 依赖反转原则,创建一个两者都知道的抽象,即TriggerHandle。由Listener/WaitSet创建并附加到Triggerable,以便它可以通过底层的ConditionNotifierTriggerHandle::notify()通知Listener/WaitSet。清理任务由m_resetCallback执行,因此Triggerable对任何Notifyable没有依赖关系。
3.3.4 Condition Variable

ConditionListenerConditionNotifier是同一类的两个不同接口,其状态存储在ConditionVariableData类中。分离的目的是为一方(例如Triggerable)提供仅用于通知Notifyable(例如Listener)的API,而Notifyable只能等待事件。因此合同在设计中得以体现。

  • 问题: 由于Listener对事件而不是状态做出反应,因此需要知道是谁通知了它。
  • 解决方案:
    • 每个TriggerHandle都有一个唯一ID,作为ConditionNotifier中的索引。
    • 当调用ConditionNotifier::notify时,Listener通过ConditionListener::wait()返回的NotificationVector_t返回值获知哪个索引通知了它。因此,与TriggerHandle的唯一ID相同,Listener知道是哪个Triggerable通知了它。
3.3.5 Event_t
并发

Listener必须能够并发地附加和分离事件。此外,它支持在回调中附加或分离进一步的事件或分离其相应的事件。此外,Listener支持在附加/分离事件时并发调用回调。

为此,我们创建了Event_t抽象,它存储在称为m_events的数组中。如果我们想要附加或分离事件,我们要么初始化`Event_t

::init(),要么重置Event_t::reset()`数组中的相应条目。数组的优点是数据结构本身在运行时不会改变,因此不必是线程安全的。

线程安全必须由Event_t类本身确保。由于每个并发操作都包含在Event_t中,我们可以使用concurrent::smart_lock结合std::recursive_mutex来保证线程安全访问。

  1. 并发附加/分离事件和回调执行,通过线程安全的Event_t确保。
  2. 通过std::recursive_mutex从回调中分离自身。
  3. 通过使用concurrent::smart_lock保护m_events中的每个Event_t对象,确保在回调中附加/分离任意事件。如果数据结构本身必须是线程安全的,这是不可能的。
生命周期

由于事件包含处理事件所需的一切,因此它有责任确保TriggerHandle的生命周期。这是通过在Event_t::reset()中调用m_invalidationCallback使相应Triggerable中的TriggerHandle无效来实现的。这在事件分离或Listener超出作用域时完成。

3.3.6 Triggerable(例如Subscriber)

Triggerable是一组类,这些类的事件可以附加到Listener。

可以将特定类的特定事件附加到Listener,或者不提供事件地附加类。

基本思路是,每当附加事件时,Listener会创建一个TriggerHandle并将其提供给相应的Triggerable。Triggerable然后使用TriggerHandle通知Listener事件的发生。

带单个事件的Triggerable

每个Triggerable需要:

  1. 私有方法:
void enableEvent(iox::popo::TriggerHandle&& triggerHandle) noexcept;
void disableEvent() noexcept;
void invalidateTrigger(const uint64_t uniqueTriggerId) noexcept;
带多个事件的Triggerable

每个Triggerable需要:

  • 使用iox::popo::EventEnumIdentifier作为底层类型的enum class
enum class EventEnum : iox::popo::EventEnumIdentifier {
  EVENT_IDENTIFIER,
  ANOTHER_EVENT_IDENTIFIER,
};
  • 私有方法:
void enableEvent(iox::popo::TriggerHandle&& triggerHandle, const EventEnum event) noexcept;
void disableEvent(const EventEnum event) noexcept;
void invalidateTrigger(const uint64_t uniqueTriggerId) noexcept;

上述方法由Listener使用,以将TriggerHandle的所有权转移到Triggerable。Triggerable应为每个可附加的事件/状态有一个TriggerHandle成员。
TriggerHandle然后用于通知Listener某个事件已发生。

  • 它必须与iox::popo::NotificationAttorney是朋友。可以提供对先前方法的公共访问,但这样用户就可以调用仅应由Listener使用的方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值