Matplotlib 为什么不能在另一个线程中绘制图形
Matplotlib 是一个流行的 Python 可视化库,用于绘制统计图、热图、轮廓图等各种类型的图表。然而,许多人在使用 Matplotlib 时都会遇到一个问题:为什么 Matplotlib 不能在另一个线程中绘制图形?
阅读更多:Matplotlib 教程
原因
要理解 Matplotlib 不能在另一个线程中绘图的原因,需要了解 Matplotlib 的工作原理。
Matplotlib 是不支持并行绘图的,每个图形实例都必须在主线程中创建并绘制。这是由于 Matplotlib 使用了基于全局状态的模型来管理子图、坐标轴和图形。多个线程同时绘图会引起这些全局状态之间的竞态条件,导致程序崩溃或图像渲染异常。
举个例子,假设我们有两个线程 A 和 B ,分别在不同的时间点调用 Matplotlib 的函数来创建和显示图形:
import threading
import matplotlib.pyplot as plt
def plot():
x = [1, 2, 3]
y = [4, 5, 6]
plt.plot(x, y)
plt.show()
t1 = threading.Thread(target=plot)
t2 = threading.Thread(target=plot)
t1.start()
t2.start()
这个程序会引起两个绘图线程同时访问 Matplotlib 的全局状态,从而导致竞态条件发生。由于 Matplotlib 在本质上是单线程应用程序,因此不能解决多线程问题,就会出现各种诡异的情况。
解决方法
虽然 Matplotlib 不能并行绘图,但是可以在多个线程中分别创建和显示图形。这意味着每个线程需要拥有自己的图形实例。可以通过以下两种方式来实现:
1. 使用进程池
可以使用 Python 标准库中的 multiprocessing 模块创建一个进程池,每个进程都可以在不同的线程中运行,从而避免线程间的竞争条件。
from multiprocessing import Pool
import matplotlib.pyplot as plt
def plot_thread(x, y):
plt.plot(x, y)
plt.show()
if __name__ == '__main__':
data = [(1, 4), (2, 5), (3, 6)]
with Pool(2) as p:
p.map(lambda x: plot_thread(*x), data)
这个程序使用了进程池来将绘图任务分配给两个进程,并行绘制图形。
2. 使用一个像Tkinter这样支持异步的GUI库
可以使用一个类似于 Tkinter 的 GUI 库,这类库支持在异步模式下绘图,可以在另一个线程中绘制图形。
import threading
import matplotlib.pyplot as plt
import tkinter as tk
class PlotApp(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
plt.ion()
self.plotting_surface = plt.figure(figsize=(5, 4), dpi=100).canvas
threading.Thread(target=self.update_plot).start()
def update_plot(self):
while True:
self.plotting_surface.draw()
self.plotting_surface.flush_events()
if __name__ == '__main__':
app = PlotApp()
app.mainloop()
这个程序使用了 Tkinter 来创建一个 GUI 窗口,并在另一个线程中更新图形。在更新过程中,程序自动调用 plt.pause() 函数,使图形在不同的时间点渲染。这与主线程中绘图是同步更新的方式不同。
总结
Matplotlib 不能在另一个线程中绘图,因为使用了基于全局状态的模型来管理子图、坐标轴和图形。这导致多个线程同时访问全局状态,引发竞态条件。解决这个问题的方法是,在不同的线程中分别创建和显示图形,或者使用一个支持异步更新的 GUI 库。这些方法都可以避免线程之间的竞争条件,实现并行绘图。