You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python多线程实现Matplotlib多幅实时图表绘制问题求解

问题原因梳理
  • 线程创建参数错误:初始代码给threading.Thread的target参数传入的是加了括号的函数调用(如thread_fgn_9()),会直接在主线程同步执行该死循环函数,子线程根本没有被创建启动,第一个函数跑起来就阻塞了后续所有逻辑。
  • Matplotlib线程安全问题:Matplotlib的GUI渲染、画布/坐标轴操作本身不是线程安全的,官方要求所有绘图相关操作必须在主线程执行,你在子线程里直接操作ax.plot、plt.pause会产生数据竞争,引发递归错误、卡顿、崩溃等异常。
  • 多进程方案不可行:进程拥有独立内存空间,主进程创建的fig、ax对象无法共享给子进程,子进程操作的只是父进程对象的拷贝,和主进程显示的画布完全无关,就算加锁也无法实现多进程操作同一张画布。
正确实现方案

采用「子线程负责数据计算 + 队列做线程间通信 + 主线程统一绘图更新」的架构,完全规避线程安全问题,步骤如下:

  1. 定义两个队列分别接收两个子线程计算出来的采样数据
  2. 子线程只做MBM采样计算,把结果扔到对应队列,不碰任何绘图相关代码
  3. 主线程用matplotlib.animation.FuncAnimation定期从队列取数据,更新已有曲线的坐标值,重绘画布(不用每次调用ax.plot新建曲线,大幅提升绘图性能)
  4. 如果需要长期运行避免内存占用过高、渲染变慢,可以在更新数据时限制历史数据长度,只保留最近N个采样点即可。

完整可运行代码

from fbm import MBM
import matplotlib.pyplot as plt
import threading
import queue
from matplotlib.animation import FuncAnimation

plt.style.use('ggplot')
fig, ax = plt.subplots(nrows=2, ncols=1, figsize=(8,6))
# 初始化两条空曲线,后续直接更新数据
line9, = ax[0].plot([], [], "g-o")
line75, = ax[1].plot([], [], "r-o")
# 初始化y轴范围,可根据实际采样值调整
ax[0].set_ylim(-2, 2)
ax[1].set_ylim(-2, 2)
ax[0].set_title("Hurst=0.9")
ax[1].set_title("Hurst=0.75")

# 定义两个队列存储子线程的计算结果
q9 = queue.Queue()
q75 = queue.Queue()

def h_90(t):
    return 0.9

def h_75(t):
    return 0.75

# 子线程仅做数据计算,不操作任何绘图对象
def calc_fgn_9():
    i = 1
    while True:
        f = MBM(n=1, hurst=h_90, length=1, method='riemannliouville')
        fgn_sample = f.mgn()
        q9.put((i, fgn_sample[0]))
        i += 1
        # 控制计算频率和绘图频率匹配
        plt.pause(0.1)

def calc_fgn_75():
    i = 1
    while True:
        f = MBM(n=1, hurst=h_75, length=1, method='riemannliouville')
        fgn_sample = f.mgn()
        q75.put((i, fgn_sample[0]))
        i += 1
        plt.pause(0.2)

# 存储历史采样数据
x9, y9 = [], []
x75, y75 = [], []

# 动画更新函数,仅在主线程执行,负责所有绘图操作
def update(frame):
    # 取出队列中所有新生成的采样数据
    while not q9.empty():
        i, val = q9.get()
        x9.append(i)
        y9.append(val)
        # 可选:限制仅保留最近100个点,提升流畅度
        # if len(x9) > 100:
        #     x9.pop(0)
        #     y9.pop(0)
    while not q75.empty():
        i, val = q75.get()
        x75.append(i)
        y75.append(val)
    # 更新曲线数据
    line9.set_data(x9, y9)
    line75.set_data(x75, y75)
    # 动态调整x轴范围
    if x9:
        ax[0].set_xlim(0, max(x9)+1)
    if x75:
        ax[1].set_xlim(0, max(x75)+1)
    return line9, line75

if __name__ == "__main__":
    # 启动计算子线程
    t1 = threading.Thread(target=calc_fgn_9, daemon=True)
    t2 = threading.Thread(target=calc_fgn_75, daemon=True)
    t1.start()
    t2.start()
    # 启动动画,主线程负责渲染
    ani = FuncAnimation(fig, update, interval=100, blit=True)
    plt.tight_layout()
    plt.show()

内容的提问来源于stack exchange,提问作者Ben Eslami

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.19 12:54:02