[架构设计] 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. 同步阻塞:所有订阅者在同一线程中顺序执行
  2. 无优先级:不支持订阅者优先级
  3. 无并发:不支持异步或并行处理
  4. 全局锁:在多线程环境可能有锁竞争

高性能实现方案

方案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. 性能调优建议

  1. 事件粒度控制

    • 高频事件(如音频帧)使用批处理
    • 低频事件(如状态变化)可以直接处理
  2. 缓冲和批处理

    python 复制代码
    # 批处理音频帧
    audio_stream.pipe(
        ops.buffer_with_time(0.1),  # 100ms 批次
        ops.filter(lambda batch: len(batch) > 0)
    ).subscribe(process_audio_batch)
  3. 背压处理

    • 使用有界队列防止内存溢出
    • 实现丢弃策略(如只保留最新N个事件)
  4. 监控和调试

    python 复制代码
    class 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)

总结

  1. pypubsub 适合简单场景,但在高性能要求下有局限性
  2. 异步事件系统 是现代 Python 应用的好选择
  3. 队列系统 适合需要解耦和优先级的场景
  4. 共享内存 适合极致性能要求
  5. 响应式编程 适合复杂的事件流处理
相关推荐
love530love5 小时前
LiveTalking 数字人项目 Windows 部署完全指南(EPGF 架构)
人工智能·windows·python·架构·livetalking·epgf
遇事不決洛必達5 小时前
【Python基础】GIL 锁是什么及其对爬虫的影响
爬虫·python·线程·进程·gil锁
CryptoPP6 小时前
快速对接东京证券交易所API数据:实战指南与代码示例
开发语言·人工智能·windows·python·信息可视化·区块链
探物 AI6 小时前
把 MambaOut 塞进 YOLOv11:会有什么样的反应
python·yolo·计算机视觉
如竟没有火炬7 小时前
最大矩阵——单调栈
数据结构·python·线性代数·算法·leetcode·矩阵
阳区欠7 小时前
【LangChain】LLM基础介绍
开发语言·python·langchain
Cosolar7 小时前
保姆级 CrewAI 教程:从零构建多智能体协作系统
人工智能·python·架构
GDAL7 小时前
使用 uv 管理 Python 版本
python·uv·版本
真实的菜7 小时前
Redis 从入门到精通(十二):典型业务场景实战 —— 排行榜、限流器、秒杀系统、Session 共享
数据库·redis·python
cup118 小时前
[开源] Meta Assistant / 告别命令行,我为一堆 Python 脚本做了一个 Windows 任务栏的“家”
windows·python·工具·nuitka·脚本运行