Почему нельзя использовать matplotlib в другой теме?

Минимальный рабочий пример

Я ожидаю, что следующий покажет сюжет, но я не вижу сюжета, и интерпретатор просто зависает (мой бэкэнд сообщает себя как 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 итераций, чтобы я мог контролировать, как меняется мое моделирование.

Psedocode ниже:

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

Наличие сюжета в главном потоке приводит к тому, что графический интерфейс графика зависает/падает, предположительно, потому что у меня другая работа продолжается. Поэтому идея заключалась в том, чтобы делать заговор в отдельном потоке.

Я видел предложения (например, здесь) для использования процесса, а не потока. Но тогда я не могу манипулировать фигурой или осями, чтобы добавить строки и т.д., Пока моя симуляция работает, потому что объект фигуры находится в удаленном процессе.

Изменить

Я не уверен, что этот вопрос является дубликатом другого, потому что этот вопрос касается того, почему pyplot api не может использоваться для управления двумя различные графики, каждый из которых находится в отдельном потоке. Это связано с тем, что условия гонки, возникающие в результате выполнения двух графиков, одновременно препятствуют pyplot определить, какая фигура является текущей фигурой.

Однако у меня есть только 1 сюжет, поэтому pyplot имеет только один и единственный текущий показатель.

Ответ 1

Как говорят другие люди, 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()

Ответ 2

Самый простой ответ:

Поскольку серверы не являются потокобезопасными. Большинство графических интерфейсов основаны на использовании методов/функций "GUI" только из одного потока ( "поток gui" ) и требуют более сложных методов при общении с разными потоками ( "рабочие потоки" ).

Вы можете найти это в документации для Qt (PyQt/PySide), wxWidgets и (не нашел более официального источника) Tkinter.