SharedMemoryManager属于multiprocessing.managers模块,用于管理共享内存,允许多个进程之间高效地共享数据,而不需要通过管道或队列传递数据的开销
基本概念
1.目的:
- 在多进程环境下,利用共享内存避免数据复制
- 提供线程安全的共享对象,允许多个进程访问
2.主要功能:
- 分配共享内存 (SharedMemory)
- 创建共享的队列或环形缓冲区,作为共享数据结构的基础
3.典型场景:
- 生产者-消费者模型
- 数据流处理系统
在代码中的使用方式
创建并启动共享内存管理器
通过 SharedMemoryManager 实例化的,用来管理共享内存资源
from multiprocessing.managers import SharedMemoryManager
with SharedMemoryManager() as shm_manager:
# 使用共享内存进行后续操作
...
创建共享队列:存储独立的离散命令
SharedMemoryQueue 被用作存储命令的队列,存储的是一组离散的命令,每个命令是独立的,彼此没有直接关系。example需要明确队列中每条命令的格式
example = {
'cmd': Command.SCHEDULE_WAYPOINT.value,
'target_pos': 0.0,
'target_time': 0.0
}
input_queue = SharedMemoryQueue.create_from_examples(
shm_manager=shm_manager, # 创建共享内存队列
examples=example, # 指定队列中数据结构和大小
buffer_size=command_queue_size# 指定整个输入队列的容量,example的数量
)
# 实时监控队列占用率
# queue_length = input_queue.length()
# print(f"Queue length: {queue_length}/{command_queue_size}")
# 如果生产者尝试 put 数据时队列已满,可能会引发异常或数据被丢弃,这时需要增大 command_queue_size
- 这里的
example
定义了队列中每条命令的基本字段和数据类型 - 包含:
cmd
:命令的类型(例如调度路径点或关闭命令)target_pos
和target_time
:每个命令的具体参数
- 队列中的每个元素代表单一任务,直接被消费者(
run
方法)提取并执行 - 数据量较小,通常只有几个字段
创建共享内存环形缓冲区:存储连续的数据流
共享内存环形缓冲区被用于存储机械手的状态信息
example = {
'gripper_state': 0,
'gripper_position': 0.0,
'gripper_velocity': 0.0,
'gripper_force': 0.0,
'gripper_measure_timestamp': time.time(),
'gripper_receive_timestamp': time.time(),
'gripper_timestamp': time.time()
} # 定义存储数据的结构
ring_buffer = SharedMemoryRingBuffer.create_from_examples(
shm_manager=shm_manager,
examples=example,
get_max_k=get_max_k, # 设置缓冲区最多存储的历史数据量
get_time_budget=0.2,
put_desired_frequency=frequency # 定义写入频率,帮助环形缓冲区管理数据吞吐
)
- 这里的
example
定义了状态数据的基本字段和类型,共同组成每次测量的完整状态 - 包含:
- 机械手的状态、位置、速度、力、时间戳等。
- 环形缓冲区存储的数据往往更复杂,可能包含多个浮点数或其他结构化数据。
- 数据必须按照固定的格式(由
example
定义)组织,以便后续插入和读取操作
注意:
队列的数据由外部生产者(主进程)生成,FIFO(先进先出),每次取出一个完整命令
缓冲区的数据由内部逻辑(如机械手反馈)生成,环形存储,覆盖旧数据
生产者-消费者模型
生产者:run
方法中的代码将机械手状态数据写入环形缓冲区
state = {
'gripper_state': info['state'],
'gripper_position': info['position'] / self.scale,
...
}
self.ring_buffer.put(state)
消费者:其他进程通过 get_state
方法读取环形缓冲区数据
def get_state(self, k=None, out=None):
if k is None:
return self.ring_buffer.get(out=out)
else:
return self.ring_buffer.get_last_k(k=k, out=out)
UMI代码中的重要设计
多进程控制
WSGController 继承自 multiprocessing.Process,用于在单独的子进程中运行机械手控制器逻辑
启动和停止:
controller = WSGController(shm_manager, hostname="robot-host", port=5556)
controller.start()
controller.stop()
上下文管理器:
with WSGController(shm_manager, hostname="robot-host", port=5556) as controller:
controller.schedule_waypoint(10.0, target_time=5.0)
命令队列
通过 SharedMemoryQueue
实现命令的异步传递:
input_queue.put()
用于生产命令input_queue.get_all()
用于消费命令
实时调度和数据流处理
PoseTrajectoryInterpolator
被用来插值机械手的运动轨迹。- 使用
precise_wait
调节循环频率,确保在设定的时间步长内完成任务
总结
SharedMemoryManager
是 Python 中多进程间共享数据的高效工具。在你的代码中,它被用来管理共享队列和环形缓冲区,构建了一个高性能的实时控制系统,主要特点包括:
- 使用
SharedMemoryQueue
处理异步命令。 - 使用
SharedMemoryRingBuffer
存储机械手状态数据。 - 通过多进程架构和上下文管理器提升代码的可维护性。
- 实现生产者-消费者模型,支持实时控制和数据流处理。
在高频控制和数据传递的应用中非常有效,避免进程间数据拷贝的开销,同时保证高效和实时性
问题分析
-
1.决策层发布频率高于控制层读取频率:
- 队列中的命令会堆积,导致控制层总是延迟处理决策层发布的命令。
- 例如,决策层在时间
t=1
到t=10
发布了 10 条命令,但控制层只能处理其中一部分,可能导致行为不连贯或延迟响应。
-
2.决策层命令不稳定:
- 如果决策层频繁更改目标,例如在
t=9s
设置了一个目标后又发布了早于t=9s
的目标(例如t=8s
),控制层可能会执行过时的命令。 - 如果控制层以队列的顺序执行命令,这种反复切换目标的行为会导致控制不稳定。
- 如果决策层频繁更改目标,例如在
-
3.控制行为异常:
- 控制层可能连续处理不一致的目标状态,例如执行完
t=10s
的控制后,又回到了过时的t=9s
状态。 - 控制器的输出会呈现随机或混乱的运动,不符合预期。
- 控制层可能连续处理不一致的目标状态,例如执行完
解决方案
1. 使用最新命令覆盖队列中的旧命令
2. 引入时间戳验证机制,只执行时间戳晚于当前状态时间的命令
3. 限制队列长度
4. 插值平滑控制目标
5. 决策层与控制层频率同步