当前位置: 首页 > 知识库问答 >
问题:

为什么matplotlib不能在其他线程中打印?

华昕
2023-03-14

我希望下面显示一个绘图,但我没有看到绘图,解释器只是挂起(我的后端报告自己为TkAgg)。

import matplotlib.pyplot as plt
from threading import Thread

def plot():
    fig, ax = plt.subplots()
    ax.plot([1,2,3], [1,2,3])
    plt.show()

def main():
    thread = Thread(target=plot)
    thread.setDaemon(True)
    thread.start()
    print 'Done'

我如何让情节显示?

我正在运行一个有很多迭代的模拟,并希望每1000次迭代更新我的图,以便我可以监控我的模拟是如何发展的。

下面的伪代码

iterations = 100000
for i in iterations:
    result = simulate(iteration=i)
    if not i % 1000:
        # Update/redraw plot here:
        # Add some lines, add some points, reset axis limits, change some colours

在主线程中使用绘图可能会导致绘图GUI挂起/崩溃,因为我还有其他工作要做。所以这个想法是在一个单独的线程中进行绘图。

我看到过使用进程而不是线程的建议(例如这里)。但是当我的模拟运行时,我不能操纵图形或轴来添加线条等,因为图形对象处于远程进程中。

我不相信这个问题与另一个问题是重复的,因为这个问题涉及到为什么不能使用pyplotapi来操作两个不同的绘图,而这两个绘图分别位于一个单独的线程上。这是因为同时执行两个图所产生的竞争条件阻止了pyplot确定哪个图是当前图。

然而,我只有一个情节,所以pyplod只有一个唯一的当前图形。

共有3个答案

蔚弘量
2023-03-14

最简单的答案可能是:

因为后端不是线程安全的。大多数GUI框架依赖于仅从一个线程(gui线程)调用GUI方法/函数,并且在与不同线程(工作线程)通信时需要更高级的方法。

您可以在Qt(PyQt/PySide)、wxWidgets和(没有找到更正式的源代码)Tkinter的文档中找到这一点。

吕皓
2023-03-14

我遇到了一个类似的问题,我想从另一个线程更新mapltolib绘图,我在这里发布了我的解决方案,以防其他人将来遇到类似的问题。

如前所述,tkagg不是线程安全的,所以你必须确保所有对matplotlib的调用都来自一个线程。这意味着线程必须通信,因此绘图线程总是执行matplotlib函数。

我的解决方案是创建一个装饰器,它将在绘图线程中执行所有装饰的函数,然后装饰所有相关的函数。这允许您做您想做的事情,而不需要对主代码中的语法进行任何更改。

i、 当你叫斧子的时候。绘图(…)在一个线程中,您将在另一个线程中自动执行它。

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()

请注意,如果忘记修饰任何函数,可能会出现分段错误。

此外,在本例中,子线程处理绘图,主线程处理模拟。一般情况下,建议做相反的操作(即让主线程拥有图形)。

太叔俊侠
2023-03-14

正如其他人所说,Matplotlib不是线程安全的,您可以选择使用多处理。您说这对您不利,因为您需要从不同的进程访问轴,但您可以通过在模拟进程和根进程之间共享数据,然后管理根进程中所有与绘图相关的活动来克服这一问题。例如

import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
import multiprocessing
import time
import random
from Tkinter import *


#Create a window
window=Tk()



def main():
    #Create a queue to share data between process
    q = multiprocessing.Queue()

    #Create and start the simulation process
    simulate=multiprocessing.Process(None,simulation,args=(q,))
    simulate.start()

    #Create the base plot
    plot()

    #Call a function to update the plot when there is new data
    updateplot(q)

    window.mainloop()
    print 'Done'


def plot():    #Function to create the base plot, make sure to make global the lines, axes, canvas and any part that you would want to update later

    global line,ax,canvas
    fig = matplotlib.figure.Figure()
    ax = fig.add_subplot(1,1,1)
    canvas = FigureCanvasTkAgg(fig, master=window)
    canvas.show()
    canvas.get_tk_widget().pack(side=TOP, fill=BOTH, expand=1)
    canvas._tkcanvas.pack(side=TOP, fill=BOTH, expand=1)
    line, = ax.plot([1,2,3], [1,2,10])




def updateplot(q):
    try:       #Try to check if there is data in the queue
        result=q.get_nowait()

        if result !='Q':
             print result
                 #here get crazy with the plotting, you have access to all the global variables that you defined in the plot function, and have the data that the simulation sent.
             line.set_ydata([1,result,10])
             ax.draw_artist(line)
             canvas.draw()
             window.after(500,updateplot,q)
        else:
             print 'done'
    except:
        print "empty"
        window.after(500,updateplot,q)


def simulation(q):
    iterations = xrange(100)
    for i in iterations:
        if not i % 10:
            time.sleep(1)
                #here send any data you want to send to the other process, can be any pickable object
            q.put(random.randint(1,10))
    q.put('Q')

if __name__ == '__main__':
    main()
 类似资料:
  • 我的问题。我试图运行matplotlib在Python 3.10在我的MacOS,但我得到以下错误: 然而,当我试着跑步的时候 然后我回来 另外,针对 终端说 此外,如果我在Sublime Text 3上使用Python 2.7.16编译代码,我可以使用matplotlib。 我的问题。如何让matplotlib在Python 3.10上运行? 因为我对Python还是新手(或者编程,就此而言),

  • 问题内容: 我知道没有线程可以访问当前视图,除非它是UI线程。我想知道为什么?哪个线程更改视图为什么很重要?是出于安全原因吗?这是我使用的解决方法: 每当我想更改布局时,这样做都是一种痛苦。是否有其他解决方法?我了解异步任务,但从未找到一种使用它的好方法,它比我正在做的更好吗?所有相关的答案都适用! 问题答案: 是的,您的权利:为了安全起见,您不能在另一个线程上修改视图(这就是为什么将其称为UI线

  • 问题内容: 我开始使用本教程为初学者学习MatPlotLib 。这是第一个例子。 如果我将这三行代码写入我的python文件并在命令行中执行(通过键入),则什么都不会发生。没有错误信息,没有情节。 有人知道我为什么看不到情节吗? 添加 当然我需要用。但是,即使我添加以下3行: 它仍然不产生任何东西。 添加 这是我现在使用的行: 我仍然有相同的结果(什么都没有)。 问题答案: 后端可能有问题。输出是

  • 问题内容: 我在新线程中创建处理程序时遇到问题。这是我的代码: 但这引发了错误!有人可以向我解释一下吗?非常感谢! 这是我的错误的详细信息: 问题答案: 您也可以这样使用: ■找一个与之相关联,因此这将不会抛出异常。

  • 来自文档:http://docs.python.org/2/library/thread 让我们在这里只讨论非守护进程线程。因为第一个引号没有特别提到非守护进程线程,所以我假设,如果主线程退出,即使是非守护进程线程也应该被杀死。然而,第二句引文却表明了另一种情况。事实上,当主线程退出时,非守护进程线程确实不会被杀死。那么,这里的第一个引用有什么意义呢?

  • 根据了解node.js事件循环,node.js支持单线程模型。这意味着如果我向node.js服务器发出多个请求,它不会为每个请求生成一个新线程,而是一个接一个地执行每个请求。这意味着如果我在node.js代码中对第一个请求执行以下操作,同时节点上出现一个新请求,第二个请求必须等到第一个请求完成,包括5秒的睡眠时间。对吗? 有没有一种方法可以让node.js为每个请求生成一个新线程,这样第二个请求就