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

Python多线程使用Queue更新pyqtgraph图表时队列填充异常

问题根因

你遇到的异常对应三个明确的代码错误:

  • q.put()打印返回None不是队列故障:Queue.put()方法设计上就没有返回值,而且你在update函数里重复调用了两次put:第一行q.put(data)已经把数据放进队列,print语句里又写了一次str(q.put(data)),等于每轮循环额外往队列塞一份重复数据,第二次调用的返回值本来就是None,打印结果完全符合预期。
  • 消费线程提前终止:你写的消费循环是while not q.empty():,这个判断只在线程启动时执行一次。线程启动初期生产者还没来得及塞入足够数据,队列短暂为空时循环条件直接不满足,消费线程就直接退出了,后续队列堆再多数据也不会被读取。
  • 跨线程操作UI触发线程安全问题:PyQt所有图形项、UI控件的更新必须在主线程(Qt主事件循环所在线程)执行,你在子线程直接调用item.set_data()属于违规操作,会打断消费线程运行,这也是你只看到两次get输出就无后续的核心原因。另外你往队列里传的是全局data列表的引用而非独立副本,多线程场景下会出现数据读写竞争。
修复方案
  1. 删除print语句里重复的q.put()调用,打印操作状态即可;往队列传数据时传入列表副本,不要传原列表引用,避免数据竞争。
  2. 消费线程改成永久阻塞等待模式,不要用q.empty()做循环条件,依靠q.get()的默认阻塞特性等待新数据,不占用CPU资源。
  3. 子线程不要直接更新UI,通过Qt的信号槽机制把数据传回主线程,由主线程完成K线图更新。
修复后可运行代码
import pyqtgraph as pg
from PyQt5 import QtWidgets, QtCore, QtGui
import sys 
import time
import threading
import queue
import random


class CandlestickItem(pg.GraphicsObject):
    def __init__(self):
        pg.GraphicsObject.__init__(self)
        self.flagHasData = False

    def set_data(self, data):
        self.data = data 
        self.flagHasData = True
        self.generatePicture()
        self.informViewBoundsChanged()

    def generatePicture(self):
        self.picture = QtGui.QPicture()
        p = QtGui.QPainter(self.picture)
        p.setPen(pg.mkPen('w'))
        w = (self.data[1][0] - self.data[0][0]) / 3.
        for (t, open, close, min, max) in self.data:
            p.drawLine(QtCore.QPointF(t, min), QtCore.QPointF(t, max))
            if open > close:
                p.setBrush(pg.mkBrush('r'))
            else:
                p.setBrush(pg.mkBrush('g'))
            p.drawRect(QtCore.QRectF(t-w, open, w*2, close-open))
        p.end()

    def paint(self, p, *args):
        if self.flagHasData:
            p.drawPicture(0, 0, self.picture)

    def boundingRect(self):
        return QtCore.QRectF(self.picture.boundingRect())

# 自定义跨线程传数据的信号类
class DataSignal(QtCore.QObject):
    update_data = QtCore.pyqtSignal(list)

app = QtWidgets.QApplication([])

data = [  
    [1., 10, 13, 5, 15],
    [2., 13, 17, 9, 20],
    [3., 17, 14, 11, 23],
    [4., 14, 15, 5, 19],
    [5., 15, 9, 8, 22],
    [6., 9, 15, 8, 16],
]
item = CandlestickItem()
item.set_data(data)

plt = pg.plot()
plt.addItem(item)
plt.setWindowTitle('pyqtgraph example: customGraphicsItem')
q = queue.Queue()
data_signal = DataSignal()
# 信号绑定UI更新函数,保证更新逻辑在主线程执行
data_signal.update_data.connect(item.set_data)

def update():
    global data
    while True:
        data_len = len(data)
        rand = random.randint(0, len(data)-1)
        new_bar = data[rand][:]
        new_bar[0] = data_len
        data.append(new_bar)
        # 传入列表副本,避免多线程数据竞争
        q.put([row[:] for row in data])
        print("putting data, current data length:", len(data))
        time.sleep(1)

def consumer_thread():
    while True:
        # 阻塞等待新数据,无需判断队列是否为空
        i = q.get()
        print("getting data, current data length:", len(i))
        # 通过信号传数据给主线程更新,禁止直接操作UI对象
        data_signal.update_data.emit(i)
        q.task_done() 

def start_threads():
    # 设置守护线程,主线程退出时自动结束子线程
    t_producer = threading.Thread(target=update, daemon=True)
    t_consumer = threading.Thread(target=consumer_thread, daemon=True)
    t_producer.start()
    t_consumer.start()
    
start_threads()

if __name__ == '__main__':
    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
        QtWidgets.QApplication.instance().exec_()
额外说明
  • 队列消费的标准写法就是while True + 阻塞式get(),不要用q.empty()做循环判断,这个方法本身在多线程场景下判断结果就不可靠,很容易出现刚判断完不为空,数据就被别的线程取走的竞态问题。
  • 所有桌面GUI框架基本都要求UI操作只能在主线程执行,跨线程直接修改UI资源大概率出现卡顿、崩溃、无响应问题,用框架自带的信号/调度机制把UI操作切回主线程是通用标准做法。
  • 多线程传递列表、字典、自定义类实例这类可变对象时,如果后续还会修改原对象,一定要传对象副本,不然其他线程拿到的是内存引用,读到的内容可能随时被别的线程修改,出现难以排查的逻辑错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 02:48:37