pypubsub 底层实现机制与高性能替代方案
文章目录
- [pypubsub 底层实现机制与高性能替代方案](#pypubsub 底层实现机制与高性能替代方案)
pypubsub 的底层实现机制
pypubsub 是一个纯 Python 实现的发布-订阅库,其底层机制相对简单:
1. 核心数据结构
python
# 简化的内部实现原理
class TopicManager:
def __init__(self):
# 主题树结构,使用字典存储
self._topics = {} # topic_name -> Topic 对象
class Topic:
def __init__(self, name):
self.name = name
# 使用弱引用列表存储订阅者,避免循环引用
self._listeners = [] # WeakMethod 或 callable 列表
2. 订阅机制
python
def subscribe(listener, topic_name):
# 1. 查找或创建主题
topic = get_or_create_topic(topic_name)
# 2. 将监听器添加到主题的订阅者列表
topic._listeners.append(WeakMethod(listener))
3. 发布机制
python
def sendMessage(topic_name, **kwargs):
# 1. 查找主题
topic = get_topic(topic_name)
if topic:
# 2. 遍历所有订阅者并调用
for listener in topic._listeners:
if listener_is_alive(listener):
listener(**kwargs)
4. 性能分析
pypubsub 的效率问题:
- 同步阻塞:所有订阅者在同一线程中顺序执行
- 无优先级:不支持订阅者优先级
- 无并发:不支持异步或并行处理
- 全局锁:在多线程环境可能有锁竞争
高性能实现方案
方案1:异步事件系统
python
import asyncio
from typing import Dict, List, Callable, Any
from collections import defaultdict
import weakref
class AsyncEventBus:
"""高性能异步事件总线"""
def __init__(self):
self._subscribers: Dict[str, List[weakref.ref]] = defaultdict(list)
self._async_subscribers: Dict[str, List[weakref.ref]] = defaultdict(list)
def subscribe(self, event: str, handler: Callable):
"""订阅事件"""
if asyncio.iscoroutinefunction(handler):
self._async_subscribers[event].append(weakref.ref(handler))
else:
self._subscribers[event].append(weakref.ref(handler))
async def publish(self, event: str, data: Any = None):
"""发布事件 - 支持并发执行"""
tasks = []
# 清理死引用
self._cleanup_dead_refs(event)
# 收集同步处理器
sync_handlers = [ref() for ref in self._subscribers[event] if ref()]
# 收集异步处理器
async_handlers = [ref() for ref in self._async_subscribers[event] if ref()]
# 并发执行异步处理器
for handler in async_handlers:
tasks.append(asyncio.create_task(handler(data)))
# 在线程池中执行同步处理器
if sync_handlers:
loop = asyncio.get_event_loop()
for handler in sync_handlers:
tasks.append(
loop.run_in_executor(None, handler, data)
)
# 等待所有任务完成
if tasks:
await asyncio.gather(*tasks, return_exceptions=True)
def _cleanup_dead_refs(self, event: str):
"""清理无效的弱引用"""
self._subscribers[event] = [
ref for ref in self._subscribers[event] if ref()
]
self._async_subscribers[event] = [
ref for ref in self._async_subscribers[event] if ref()
]
优势:
- 支持同步和异步处理器
- 并发执行,提高吞吐量
- 自动清理死引用
方案2:基于队列的解耦系统
python
import queue
import threading
from concurrent.futures import ThreadPoolExecutor
from dataclasses import dataclass
from typing import Dict, Callable
import time
@dataclass
class Event:
name: str
data: Any
timestamp: float = None
priority: int = 0
def __post_init__(self):
if self.timestamp is None:
self.timestamp = time.time()
def __lt__(self, other):
# 支持优先级队列
return self.priority > other.priority
class QueueBasedEventBus:
"""基于队列的高性能事件总线"""
def __init__(self, num_workers: int = 4):
self._event_queue = queue.PriorityQueue()
self._subscribers: Dict[str, List[Callable]] = defaultdict(list)
self._executor = ThreadPoolExecutor(max_workers=num_workers)
self._running = True
self._workers = []
# 启动工作线程
for _ in range(num_workers):
worker = threading.Thread(target=self._worker_loop)
worker.daemon = True
worker.start()
self._workers.append(worker)
def subscribe(self, event_name: str, handler: Callable,
filter_func: Callable = None):
"""订阅事件,可选过滤器"""
self._subscribers[event_name].append({
'handler': handler,
'filter': filter_func
})
def publish(self, event_name: str, data: Any = None,
priority: int = 0):
"""发布事件到队列"""
event = Event(name=event_name, data=data, priority=priority)
self._event_queue.put(event)
def _worker_loop(self):
"""工作线程主循环"""
while self._running:
try:
# 获取事件(带超时)
event = self._event_queue.get(timeout=0.1)
# 处理事件
self._process_event(event)
# 标记完成
self._event_queue.task_done()
except queue.Empty:
continue
def _process_event(self, event: Event):
"""处理单个事件"""
subscribers = self._subscribers.get(event.name, [])
for sub in subscribers:
# 应用过滤器
if sub['filter'] and not sub['filter'](event):
continue
# 在线程池中执行处理器
self._executor.submit(
self._safe_call, sub['handler'], event
)
def _safe_call(self, handler: Callable, event: Event):
"""安全调用处理器"""
try:
handler(event)
except Exception as e:
print(f"Handler error: {e}")
优势:
- 支持事件优先级
- 发布者不阻塞
- 可配置工作线程数
- 支持事件过滤
方案3:零拷贝共享内存方案
python
import mmap
import struct
import threading
from typing import Dict, List, Callable
import pickle
class SharedMemoryEventBus:
"""基于共享内存的超高性能事件总线"""
def __init__(self, size: int = 10 * 1024 * 1024): # 10MB
# 创建共享内存
self._shm = mmap.mmap(-1, size)
self._lock = threading.Lock()
self._read_pos = 0
self._write_pos = 0
self._subscribers: Dict[str, List[Callable]] = defaultdict(list)
# 启动读取线程
self._reader_thread = threading.Thread(target=self._reader_loop)
self._reader_thread.daemon = True
self._reader_thread.start()
def publish_bytes(self, event_name: str, data: bytes):
"""发布二进制数据 - 零拷贝"""
with self._lock:
# 写入事件头:[长度(4) | 名称长度(2) | 名称 | 数据]
event_name_bytes = event_name.encode('utf-8')
total_len = 6 + len(event_name_bytes) + len(data)
# 写入到共享内存
header = struct.pack('!IH', total_len, len(event_name_bytes))
self._shm[self._write_pos:self._write_pos + 6] = header
self._write_pos += 6
self._shm[self._write_pos:self._write_pos + len(event_name_bytes)] = event_name_bytes
self._write_pos += len(event_name_bytes)
self._shm[self._write_pos:self._write_pos + len(data)] = data
self._write_pos += len(data)
def _reader_loop(self):
"""读取循环"""
while True:
with self._lock:
if self._read_pos < self._write_pos:
# 读取事件头
header = self._shm[self._read_pos:self._read_pos + 6]
total_len, name_len = struct.unpack('!IH', header)
self._read_pos += 6
# 读取事件名
event_name = self._shm[self._read_pos:self._read_pos + name_len].decode('utf-8')
self._read_pos += name_len
# 读取数据
data_len = total_len - 6 - name_len
data = self._shm[self._read_pos:self._read_pos + data_len]
self._read_pos += data_len
# 分发给订阅者
self._dispatch(event_name, data)
优势:
- 极高性能,零拷贝
- 适合大数据传输
- 低延迟
缺点:
- 仅限单机
- 实现复杂
- 需要手动管理内存
方案4:响应式编程模式 (RxPY)
python
from rx import create, operators as ops
from rx.subject import Subject
from typing import Dict
class ReactiveEventBus:
"""基于响应式编程的事件总线"""
def __init__(self):
self._subjects: Dict[str, Subject] = {}
def get_stream(self, event_name: str) -> Subject:
"""获取事件流"""
if event_name not in self._subjects:
self._subjects[event_name] = Subject()
return self._subjects[event_name]
def publish(self, event_name: str, data: Any):
"""发布事件"""
if event_name in self._subjects:
self._subjects[event_name].on_next(data)
# 使用示例
def example_usage(self):
# 订阅并处理事件流
self.get_stream("user_action").pipe(
ops.filter(lambda x: x['type'] == 'click'),
ops.throttle_first(1.0), # 限流
ops.map(lambda x: x['data']),
ops.buffer_with_time(5.0) # 5秒批处理
).subscribe(
on_next=lambda batch: print(f"处理批次: {batch}")
)
# 组合多个事件流
click_stream = self.get_stream("click")
mouse_stream = self.get_stream("mouse_move")
# 合并流并处理
rx.merge(click_stream, mouse_stream).pipe(
ops.timestamp(),
ops.scan(lambda acc, x: acc + 1, 0) # 计数
).subscribe(
on_next=lambda x: print(f"事件总数: {x}")
)
优势:
- 强大的操作符
- 易于组合和转换
- 内置背压处理
- 支持复杂的事件流处理
性能对比
| 方案 | 吞吐量 | 延迟 | CPU开销 | 内存开销 | 复杂度 | 适用场景 |
|---|---|---|---|---|---|---|
| pypubsub | 低 | 中 | 低 | 低 | 简单 | 小型应用、简单场景 |
| 异步事件系统 | 高 | 低 | 中 | 中 | 中等 | 高性能Web应用、IO密集型 |
| 队列系统 | 中高 | 中 | 中 | 中高 | 中等 | 复杂业务系统、需要优先级 |
| 共享内存 | 极高 | 极低 | 低 | 高 | 复杂 | 高频交易、实时系统 |
| 响应式 | 高 | 低 | 中 | 中 | 复杂 | 复杂事件处理、流处理 |
在 Robot 项目中的应用建议
对于Robot项目的特点(实时音频、机器人控制、多模块协作),建议采用混合架构:
1. 分层事件系统
python
from typing import Dict, Any
from pubsub import pub
import asyncio
class RobotEventSystem:
"""项目的混合事件系统"""
def __init__(self):
# 1. pypubsub 用于简单的状态通知
# 适合:机器人状态变化、配置更新等低频事件
self.simple_pub = pub
# 2. 异步总线用于音频处理
# 适合:音频帧处理、实时语音识别结果
self.audio_bus = AsyncEventBus()
# 3. 队列系统用于机器人动作
# 适合:动作排队、优先级控制
self.robot_queue = QueueBasedEventBus(num_workers=2)
# 4. 共享内存用于音频数据传输(可选)
# 适合:大量音频数据的进程间传输
self.audio_shm = SharedMemoryEventBus() if USE_SHM else None
async def handle_audio_event(self, audio_data: bytes):
"""高性能音频处理"""
if self.audio_shm:
# 使用共享内存传输原始音频数据
self.audio_shm.publish_bytes("audio.raw", audio_data)
else:
# 使用异步总线传输
await self.audio_bus.publish("audio.frame", audio_data)
def queue_robot_action(self, action: str, priority: int = 0):
"""机器人动作排队"""
self.robot_queue.publish("robot.action", {
"action": action,
"timestamp": time.time()
}, priority=priority)
def notify_state_change(self, state: str, data: Dict[str, Any]):
"""状态变化通知 - 使用简单的 pypubsub"""
self.simple_pub.sendMessage(f"state.{state}", data=data)
2. 模块集成示例
python
# audio_module.py
class AudioModule:
def __init__(self, event_system: RobotEventSystem):
self.events = event_system
# 订阅音频相关事件
asyncio.create_task(self._subscribe_audio_events())
async def _subscribe_audio_events(self):
"""订阅音频事件"""
self.events.audio_bus.subscribe(
"wakeup.detected",
self._on_wakeup_detected
)
async def _on_wakeup_detected(self, data):
"""处理唤醒词检测"""
# 高优先级通知机器人
self.events.queue_robot_action("turn_to_sound", priority=10)
# robot_module.py
class RobotModule:
def __init__(self, event_system: RobotEventSystem):
self.events = event_system
# 使用 pypubsub 订阅状态
pub.subscribe(self._on_audio_playing, "state.audio_playing")
def _on_audio_playing(self, data):
"""音频播放时暂停机器人动作"""
if data.get("playing"):
self.pause_actions()
3. 性能调优建议
-
事件粒度控制
- 高频事件(如音频帧)使用批处理
- 低频事件(如状态变化)可以直接处理
-
缓冲和批处理
python# 批处理音频帧 audio_stream.pipe( ops.buffer_with_time(0.1), # 100ms 批次 ops.filter(lambda batch: len(batch) > 0) ).subscribe(process_audio_batch) -
背压处理
- 使用有界队列防止内存溢出
- 实现丢弃策略(如只保留最新N个事件)
-
监控和调试
pythonclass MonitoredEventBus(AsyncEventBus): def __init__(self): super().__init__() self.metrics = { "published": 0, "processed": 0, "errors": 0 } async def publish(self, event: str, data: Any = None): self.metrics["published"] += 1 await super().publish(event, data)
总结
- pypubsub 适合简单场景,但在高性能要求下有局限性
- 异步事件系统 是现代 Python 应用的好选择
- 队列系统 适合需要解耦和优先级的场景
- 共享内存 适合极致性能要求
- 响应式编程 适合复杂的事件流处理