Python - ProcessPoolExecutor зависает при вызове из обработчика mpl_connect - PullRequest
1 голос
/ 27 февраля 2020

Я использую параллельную обработку для генерации графика функций с использованием комплексных чисел. Мой сценарий позволяет вам увеличивать область графика, используя стандартные элементы управления matplotlib, а затем восстанавливать график в новых пределах для улучшения разрешения.

Это мой первый опыт параллельной обработки, и я получил насколько я понимаю, что мне нужно ввести предисловие if __name__ == __main__:, чтобы разрешить импорт модуля должным образом. При запуске моего сценария первый график успешно генерируется и выглядит так, как ожидалось. Однако, когда функция построения вызывается снова из моего обработчика событий, она вместо этого зависает бесконечно. Я предполагаю, что зависание вызвано некоторой схожей с требованием if __name__ == __main__: проблемой, так как параллельные процессы порождаются вне основной части скрипта, но я не нашел ничего более этого.

import numpy as np
import matplotlib.pyplot as plt
from concurrent.futures import ProcessPoolExecutor
import multiprocessing

res = [1000, 1000]
base_factor = 2.
cpuNum = multiprocessing.cpu_count()

def brot(c, depth=200):
    z = complex(0)

    for i in range(depth):
        z = pow(z, 2) + c

        if abs(z) > 2:
            return i

    return -1

def brot_gen(span):
    re_span = span[0]
    im_span = span[1]

    mset = np.zeros([len(im_span), len(re_span)])

    for re in range(len(re_span)):
        for im in range(len(im_span)):
            mset[im][re] = brot(complex(re_span[re], im_span[im]))

    return mset

def brot_gen_parallel(re_lim, im_lim):
    re_span = np.linspace(re_lim[0], re_lim[1], res[0])
    im_span = np.linspace(im_lim[0], im_lim[1], res[1])

    split_re_span = np.array_split(re_span, cpuNum)

    packages = [(sec, im_span) for sec in split_re_span]
    print("Generating set between", re_lim, "and", im_lim, "...")

    with ProcessPoolExecutor(max_workers = cpuNum) as executor:
            result = executor.map(brot_gen, packages)

    mset = np.concatenate(list(result), axis=1)
    print("Set generated")

    return mset

def handler(ax):
    def action(event):
        if event.button == 2:
            cur_re_lim = ax.get_xlim()
            cur_im_lim = ax.get_ylim()

            mset = brot_gen_parallel(cur_re_lim, cur_im_lim)

            ax.cla()
            ax.imshow(mset, extent=[cur_re_lim[0], cur_re_lim[1], cur_im_lim[0], cur_im_lim[1]], origin="lower", vmin=0, vmax=200, interpolation="bilinear")

            plt.draw()

    fig = ax.get_figure()
    fig.canvas.mpl_connect('button_release_event', action)

    return action

if __name__ == "__main__":
    re_lim = np.array([-2.5, 2.5])
    im_lim = res[1]/res[0] * re_lim

    mset = brot_gen_parallel(re_lim, im_lim)

    plt.imshow(mset, extent=[re_lim[0], re_lim[1], im_lim[0], im_lim[1]], origin="lower", vmin=0, vmax=200, interpolation="bilinear")

    ax = plt.gca()

    f = handler(ax)

    plt.show()

РЕДАКТИРОВАТЬ: я задавался вопросом, была ли ошибка в коде, вызывающая исключение, но что это не может быть успешно передано обратно на консоль, однако я протестировал это, выполнив ту же задачу, не разбивая ее на параллельные задачи, и она успешно выполнена.

1 Ответ

0 голосов
/ 29 февраля 2020

Я нашел ответ на свой вопрос. Ответ лежит в IDE, которую я использовал. По моему опыту, в большинстве IDE plt.show() блокирует выполнение по умолчанию, однако в Spyder значение по умолчанию кажется эквивалентом plt.show(block=False), что означает, что сценарий завершен, и поэтому все, что требовалось для успешного запуска параллельных процессов, больше не было доступно вызывая зависание. Это было решено простым изменением оператора на plt.show(block=True), что означало, что сценарий все еще действовал.

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

...