我有一个类似的问题,我想从不同的线程更新 mapltolib 图,我在这里发布我的解决方案,以防其他人将来遇到类似的问题。
如前所述,tkagg 不是线程安全的,因此您必须确保对 matplotlib 的所有调用都来自单个线程。这意味着线程必须进行通信,以便“绘图线程”始终执行 matplotlib 函数。
我的解决方案是创建一个装饰器,它将执行“绘图线程”中的所有装饰功能,然后装饰所有相关功能。这使您可以在不更改主代码中的语法的情况下做您想做的事情。
即当您在一个线程中调用 ax.plot(...) 时,您将在另一个线程中自动执行它。
import matplotlib.pyplot as plt
import matplotlib
import threading
import time
import queue
import functools
#ript(Run In Plotting Thread) decorator
def ript(function):
def ript_this(*args, **kwargs):
global send_queue, return_queue, plot_thread
if threading.currentThread() == plot_thread: #if called from the plotting thread -> execute
return function(*args, **kwargs)
else: #if called from a diffrent thread -> send function to queue
send_queue.put(functools.partial(function, *args, **kwargs))
return_parameters = return_queue.get(True) # blocking (wait for return value)
return return_parameters
return ript_this
#list functions in matplotlib you will use
functions_to_decorate = [[matplotlib.axes.Axes,'plot'],
[matplotlib.figure.Figure,'savefig'],
[matplotlib.backends.backend_tkagg.FigureCanvasTkAgg,'draw'],
]
#add the decorator to the functions
for function in functions_to_decorate:
setattr(function[0], function[1], ript(getattr(function[0], function[1])))
# function that checks the send_queue and executes any functions found
def update_figure(window, send_queue, return_queue):
try:
callback = send_queue.get(False) # get function from queue, false=doesn't block
return_parameters = callback() # run function from queue
return_queue.put(return_parameters)
except:
None
window.after(10, update_figure, window, send_queue, return_queue)
# function to start plot thread
def plot():
# we use these global variables because we need to access them from within the decorator
global plot_thread, send_queue, return_queue
return_queue = queue.Queue()
send_queue = queue.Queue()
plot_thread=threading.currentThread()
# we use these global variables because we need to access them from the main thread
global ax, fig
fig, ax = plt.subplots()
# we need the matplotlib window in order to access the main loop
window=plt.get_current_fig_manager().window
# we use window.after to check the queue periodically
window.after(10, update_figure, window, send_queue, return_queue)
# we start the main loop with plt.plot()
plt.show()
def main():
#start the plot and open the window
thread = threading.Thread(target=plot)
thread.setDaemon(True)
thread.start()
time.sleep(1) #we need the other thread to set 'fig' and 'ax' before we continue
#run the simulation and add things to the plot
global ax, fig
for i in range(10):
ax.plot([1,i+1], [1,(i+1)**0.5])
fig.canvas.draw()
fig.savefig('updated_figure.png')
time.sleep(1)
print('Done')
thread.join() #wait for user to close window
main()
请注意,如果您忘记装饰任何函数,您可能会遇到分段错误。
此外,在此示例中,子线程处理绘图,主线程处理模拟。一般来说,建议做相反的事情,(即让主线程有图形)。