到目前为止,我想将以下示例程序标记为我的问题的答案。它绝对不是完美的,或者它甚至不是在 Python 和 matplotlib 中做到这一点的正确方法。
我认为不引起图上无响应的重要一点是不要挂起“UI”线程,当显示 UI 时,matplotlib 可能正在其上运行事件循环,所以如果我放任何 @ 987654323@ 或调用Queue.get() 阻塞线程执行,图形窗口将挂起。
因此,我没有在“Queue.get()”处阻塞线程,而是选择使用“Queue.get_nowait()”作为传入新数据的轮询方法。 UI线程(即matplotlib图形窗口更新工作者)只会阻塞在matplotlib.pyplot.pause(),我相信这不会暂停事件循环。
如果matplotlib 中有另一个调用可以阻塞并等待信号,我认为这会比这种轮询方式更好。
起初我看到multiprocessing 和matplotlib 的例子,所以我试图使用多个进程来实现并发。但是好像你只需要自己处理同步,用多线程代替就可以了。多线程的好处是在一个进程内共享数据。所以下面的程序使用了threading模块而不是multiprocessing。
以下是我的测试程序,我可以在Windows 7(64位)和Python 2.7上运行,并且图形窗口在这个更新速度下是响应式的,你可以拖动它,调整它的大小等等。
#!/usr/bin/python
# vim: set fileencoding=utf-8:
import random
import time
import Queue
import threading
import numpy as np
import matplotlib.pyplot as plt
## Measurement data that are shared among threads
val1 = []
val2 = []
lock = threading.Lock()
def update_data_sync(x, y):
lock.acquire()
val1.append(x)
val2.append(y)
if len(val1) > 50:
del val1[0]
if len(val2) > 50:
del val2[0]
lock.release()
def get_data_sync():
lock.acquire()
v1 = list(val1)
v2 = list(val2)
lock.release()
return (v1, v2)
def worker(queue):
plt.ion()
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.margins(0.05, 0.05)
#ax.set_autoscale_on(True)
ax.autoscale(enable=True, axis='both')
#ax.autoscale(enable=True, axis='y')
ax.set_ylim(0, 1)
line1, line2 = ax.plot([], [], 'b-', [], [], 'r-')
while True:
need_draw = False
is_bye = False
while True:
## Try to exhaust all pending messages
try:
msg = queue.get_nowait()
if msg is None:
print "thread: FATAL, unexpected"
sys.exit(1)
if msg == 'BYE':
print "thread: got BYE"
is_bye = True
break
# Assume default message is just let me draw
need_draw = True
except Queue.Empty as e:
# Not 'GO' or 'BYE'
break
## Flow control
if is_bye:
break
if not need_draw:
plt.pause(0.33)
continue;
## Draw it
(v1, v2) = get_data_sync()
line1.set_xdata(range(1, len(v1) + 1, 1))
# Make a clone of the list to avoid competition on the same dataset
line1.set_ydata(v1)
line2.set_xdata(line1.get_xdata())
line2.set_ydata(v2)
## Adjust view
#ax.set_xlim(0, len(line1.get_ydata()) + 1)
#ax.set_ylim(0, 1)
## (??) `autoscale' does not work here...
#ax.autoscale(enable=True, axis='x')
#ax.autoscale(enable=True, axis='y')
ax.relim()
ax.autoscale_view(tight=True, scalex=True, scaley=False)
## "Redraw"
## (??) Maybe pyplot.pause() can ensure visible redraw
fig.canvas.draw()
print "thread: DRAW"
plt.pause(0.05)
## Holy lengthy outermost `while' loop ends here
print "thread: wait on GUI"
plt.show(block=True)
plt.close('all')
print "thread: worker exit"
return
def acquire_data():
# Fake data for testing
if not hasattr(acquire_data, 'x0'):
acquire_data.x0 = 0.5
x = int(random.random() * 100) / 100.0
while np.abs(x - acquire_data.x0) > 0.5:
x = int(random.random() * 100) / 100.0
acquire_data.x0 = x
y = 0.75 * np.abs(np.cos(i * np.pi / 10)) + 0.15
return (x, y)
if __name__ == "__main__":
queue = Queue.Queue()
thr = threading.Thread(target=worker, args=(queue, ))
thr.start()
for i in range(200):
(x, y) = acquire_data()
update_data_sync(x, y)
#print "main: val1: {}. val2: {}".format(x, y)
queue.put("GO")
time.sleep(0.1)
queue.put('BYE')
print "main: waiting for children"
thr.join()
print "main: farewell"