のモジュールとPool.map
の組み合わせに問題があります。UI ブレークでより大きなワークロードを計算すると、デフォルトの画面に反応しなくなります。押されたキーを即座に読み取る (そして解析を続行する) 代わりに、Enter キーを押すまで任意の数のキーを押すことができます。時々 (それに加えて) UI が壊れることさえあります (通常のシェルの一部を表示するなど)。curses
Python
Pool.map
curses
getch
Curses UI ラッパー
Screen
これは、curses 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
上記の失敗は、UI 内で結合し、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)
と、10,000,000 列の 8 行に相当します)curse
がgetch
中断し、次の動作が発生します。
q
ご覧のとおり、何度でも (または他のキーを)curse
叩くことができますが、getch
は反応しません。入力を認識させるには、Enter キーを押す必要があります。
さらに、何らかの理由で最初の行が元のシェルの出力で上書きされます。
この動作は、おおよそ の計算にPool.map
1 秒以上かかる場合にのみ発生します。
data_arr
すべてが魔法のように機能するような小さな配列に設定するnp.random.rand(8, 100)
と、計算に1秒以上かかる大きな配列をフィードするとすぐに、この奇妙なバグが現れてcurses
UIが壊れます。
何か案は?
Pool.map
どういうわけかワーカープロセスに正しく参加していませんか?