3 线程模型与接口设计
3.1 概述
这个章节的本意是说明 ag_driver 的接口设计,而不是描述它的线程模型。
要求使用者了解 ag_driver 的内存运行机制,至少理论上是不应该的。但这里还是这么做了。
坚持这样做的原因是:
使用者实现的回调函数运行在
ag_driver的线程中,设计不当可能导致ag_driver丢包。ag_driver希望使用者自己管理点云实例,以避免点云的复制,反复分配和释放。这一点与它对 MSOP/DIFOP Packet 的处理类似。
所以先讲清楚线程模型,能帮助使用者更好地了解接口背后的设计考虑。
3.2 组件与线程
ag_driver 主要由三部分组成: Input、Decoder、LidarDriverImpl。
Input部分负责从 Socket、PCAP 文件等数据源,获取 MSOP/DIFOP Packet。Input的类一般有自己的接收线程recv_thread。Decoder部分负责解析 MSOP/DIFOP Packet,得到点云。Decoder部分没有自己的线程,它运行在LiarDriverImpl的 Packet 处理线程handle_thread中。LidarDrvierImpl部分将Input和Decoder组合到一起。它从Input得到 Packet,根据 Packet 的类型将它派发到Decoder。得到点云后,通过用户的回调函数传递给用户。

这里有两点值得说明:
LidarDriverImpl管理一个空闲 MSOP/DIFOP Packet 的队列free_pkt_queue,和一个填充了的 Packet 的队列pkt_queue。Input接收前从free_pkt_queue得到空闲的 Packet 实例,接收后将填充好的 Packet 保存到pkt_queue,这两者都通过LidarDriverImpl的两个回调函数完成。LidarDriverImpl的回调函数实际上运行在Input的线程recv_thread中,它们只是简单从队列中取出 Packet 实例,或将 Packet 实例放入队列。这是简单的工作,不会拖慢recv_thread的运行。
将 Packet 队列的设计想法平移到点云队列,就得到 ag_driver 的接口设计了。
点云实例由调用者的代码自己管理,
ag_driver只要求两个回调函数,一个得到空闲的点云实例,一个返回填充好的点云。调用者的代码可以借鉴 Packet 队列的做法,管理两个点云队列,但这点并不强制要求。调用者实现的两个回调函数,运行在 ag_driver 的线程
handle_thread中。这个线程负责解析 MSOP/DIFOP Packet,如果不能及时处理它们,pkt_queue就会溢出,ag_driver除了报告ERRCODE_PKTBUFOVERFLOW之外,还会丢掉处理不了的 Packet。所以使用者的回调函数不能做任何太耗时的事情。
3.3 接口设计
这样 ag_driver 接口设计的目标就清楚了。
避免点云的复制、避免点云的反复分配和释放;
让点云的构建(填充)与点云的处理并行,两者不要互相干扰。
下图是设想的 ag_driver 与调用者之间交互的方式。图中略去了 ag_driver 的大部分实现细节,只留下 MSOP/DIFOP Packet 的处理线程 handle_thread。它同时也是点云的构建线程,这里以点云为说明对象,所以重命名为 construct_thread。

ag_driver 运行在线程 construct_thread 中。它
从调用者得到空闲的点云实例。调用者从一个空闲的点云队列
free_point_cloud_queue得到这个实例。如果队列为空,则创建一个新的。解析 MSOP/DIFOP Packet,构建(填充)点云实例。
将填充好的点云返还给调用者。
调用者将这个实例转移到另一个点云队列
stuffed_point_cloud_queue,等待处理。
调用者的点云处理代码,运行在自己的线程 process_thread 中。它
从待处理点云队列
stuffed_point_cloud_queue,取出一个点云实例。处理这个点云实例。
处理后,将它放回空闲队列
free_point_cloud_queue,等待ag_driver再次使用。