2016-12-23 3 views
0

У меня проблема с Pool.map в сочетании с модулем curses от Python. Всякий раз, когда я вычисляю большую нагрузку с Pool.map, мой curses пользовательский интерфейс нарушает: он больше не реагирует на экран getch по умолчанию. Вместо того, чтобы читать в любой нажатой клавише мгновенно (и продолжая ее разбор), я могу нажать любое количество клавиш, пока не нажму кнопку enter. Иногда (в дополнение к этому) даже пользовательский интерфейс ломается (например, показывая часть моей нормальной оболочки).Python curses: проблема многопроцессорности с Pool.map?


Проклятия UI обертка

Это класс-оболочка (Screen), который обрабатывает материал проклятий UI для меня:

# -*- coding: utf-8 -*- 
import curses 



class Screen(object): 

    def __init__(self): 
     # create a default screen 
     self.__mainscr = curses.initscr() 
     self.__stdscr = curses.newwin(curses.LINES - 2, curses.COLS - 2, 1, 1) 
     self.__max_height, self.__max_width = self.__stdscr.getmaxyx() 

     # start coloring 
     curses.start_color() 
     curses.use_default_colors() 

     # define colors 
     curses.init_pair(1, 197, -1) # red 
     curses.init_pair(2, 227, -1) # yellow 
     curses.init_pair(3, curses.COLOR_MAGENTA, -1) 
     curses.init_pair(4, curses.COLOR_GREEN, -1) # darkgreen 
     curses.init_pair(5, curses.COLOR_BLUE, -1) 
     curses.init_pair(6, curses.COLOR_BLACK, curses.COLOR_WHITE) 
     curses.init_pair(7, curses.COLOR_WHITE, -1) 
     curses.init_pair(8, curses.COLOR_CYAN, -1) 
     curses.init_pair(9, 209, -1) # orange 
     curses.init_pair(10, 47, -1) # green 


    def add_str_to_scr(self, add_str: str, colorpair: int = 7): 
     self.__stdscr.addstr(str(add_str), curses.color_pair(colorpair)) 


    def linebreak(self): 
     self.__stdscr.addstr("\n") 


    def clear_screen(self): 
     self.__stdscr.clear() 


    def refresh_screen(self): 
     self.__stdscr.refresh() 


    def wait_for_enter_or_esc(self): 
     curses.noecho() 
     while True: 
      c = self.__stdscr.getch() 
      if c == 10 or c == 27: # 10: Enter, 27: ESC 
       break 
     curses.echo() 


    def get_user_input_chr(self) -> str: 
     return chr(self.__stdscr.getch()) 


    def get_user_input_str(self) -> str: 
     return self.__stdscr.getstr().decode(encoding="utf-8") 

Фактическая программа

Я написал небольшой пример, так как упомянутый отказ всегда происходит, когда я совмещаю Pool.map в пользовательском интерфейсе curses и имею большую рабочую нагрузку. Код просто вычисляет некоторые бесполезные элементы mult и add на массиве numpy.

import curses 
from screen import Screen 
from multiprocessing import Pool, cpu_count 
import numpy as np 

s = Screen()   # initializing my Screen wrapper 
np.random.seed(1234) # setting the rng fixed to make results comparable 

# worker function to simulate workload 
def worker(arr): 
    return arr * 2 + 1 

s.clear_screen() # cleans the screen 
s.refresh_screen() # displays current buffer's content 

s.add_str_to_scr("Start processing data...") 
s.linebreak() 
s.linebreak() 
s.refresh_screen() 

# data to feed worker function with (sliced by rows) 
data_arr = np.random.rand(8, int(1e7)) # <-- big array for high workload 

with Pool(cpu_count()) as p: 
    buffer = p.map(worker, [data_arr[row] for row in np.ndindex(data_arr.shape[0])]) 

s.add_str_to_scr("...finished processing:") 
s.linebreak() 
s.linebreak() 
s.refresh_screen() 
for row in buffer: 
    s.add_str_to_scr(row[0:3]) 
    s.linebreak() 
    s.refresh_screen() 

# *Here* the program should wait until the user presses *any* key 
# and continue INSTANTLY when any key gets pressed. 
# However, for big workloads, it does not react to single key presses, 
# but wait for any amount of keys pressed until you hit 'Enter' 
s.get_user_input_chr() 
curses.endwin() 

Теперь, когда я выполняю код с высокой нагрузкой (т.е. хруст массив формы (8, int(1e7) составляет 8 строк с 10000000 столбцов) curse «s getch перерывы, и я получаю такое поведение:

enter image description here

Как вы можете видеть, я могу ударить q (или любой другой ключ) так часто, как я хочу, но curse «s getch не реагирует. Я должен нажать клавишу Enter, чтобы распознать вход.

Кроме того, по какой-то причине первая строка получает замену исходной оболочки.

Такое поведение наблюдается, когда расчет Pool.map примерно равен 1 секунде или более.

Когда я установил data_arr в небольшой массив как np.random.rand(8, 100) все работает как шарм, но как только я кормлю большие массивы, где вычисление происходит, как> = 1second, появляется эта странная ошибка и ломает мой curses UI.

Любые идеи?

Является ли Pool.map неправильным присоединением к рабочим процессам?

+0

Вы пробовали интерфейсы более высокого уровня для проклятий, например. благословения? https://pypi.python.org/pypi/blessings/ В любом случае этого плохого поведения там нет. –

+0

Спасибо за подсказку, я попробую. Тем не менее, мне действительно интересно, почему возникает эта странная проблема ... имеет проклятие какой-то внутренний цикл, который не может слишком долго ждать, пока другие процессы не закончатся? Мне кажется странным. – daniel451

+0

* благословения * имеет некоторый диапазон использования (и это не «более высокий уровень» и т. Д.), Но не решает этот вопрос. –

ответ

1

Программа делает то, что вы сказали это сделать:

  • вы звоните initscr (и игнорируя тот факт, что проклинает создает окно верхнего уровня),
  • затем создать подокно, которая охватывает большая часть экрана,
  • печать нескольких строк на экране, обновление дисплея после каждой строки, и
  • , наконец, в конце, ожидая ввода из подзаголовка.

Однако программа не вызывает cbreakraw, и т.д., которые позволили бы вам прочитать небуферизованный (нет «Enter» нажимается) характер. Кроме того, программа не отключает echo. Если нагрузка светлая, вы не заметите, так как реакция выполняется быстро. Но при большой нагрузке, например, при обмене или высокой загрузке памяти/ЦП, она все равно будет восстанавливаться, когда она попадет в приглашение. Итак, вы замечаете.

Что касается размера экрана, возможно, вы имели в виду

self.__stdscr = curses.newwin(curses.LINES - 1, curses.COLS - 1, 0, 0) 

, но предположим, что вы намеревались оставить «пустое» пространство вокруг окна, вы могли бы улучшить положение вещей, делая

 self.__mainscr.refresh() 

сразу после initscr (что бы стереть экран).

+0

спасибо за подсказку! Действительно, когда я использую 'cbreak' и' noecho', основная проблема исчезла. Тем не менее, 'curses' все еще нарушает пользовательский интерфейс, отображая часть моей исходной оболочки. Любая идея для этого? – daniel451

+0

Ваш stdscr не охватывает весь экран - он начинается со второй строки экрана. Номера строк/столбцов начинаются с нуля. –

+0

... но не должен 'mainscr' охватывать весь терминал? Более того, эта проблема возникает только с большим «data_arr». Всякий раз, когда он мал (таким образом, время вычисления составляет всего миллисекунды), проблема не возникает. – daniel451

Смежные вопросы