1

のモジュールとPool.mapの組み合わせに問題があります。UI ブレークでより大きなワークロードを計算すると、デフォルトの画面に反応しなくなります。押されたキーを即座に読み取る (そして解析を続行する) 代わりに、Enter キーを押すまで任意の数のキーを押すことができます。時々 (それに加えて) UI が壊れることさえあります (通常のシェルの一部を表示するなど)。cursesPythonPool.mapcursesgetch


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計算するだけです。addnumpy

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 行に相当します)cursegetch中断し、次の動作が発生します。

ここに画像の説明を入力

qご覧のとおり、何度でも (または他のキーを)curse叩くことができますが、getchは反応しません。入力を認識させるには、Enter キーを押す必要があります。

さらに、何らかの理由で最初の行が元のシェルの出力で上書きされます。

この動作は、おおよそ の計算にPool.map1 秒以上かかる場合にのみ発生します。

data_arrすべてが魔法のように機能するような小さな配列に設定するnp.random.rand(8, 100)と、計算に1秒以上かかる大きな配列をフィードするとすぐに、この奇妙なバグが現れてcursesUIが壊れます。

何か案は?

Pool.mapどういうわけかワーカープロセスに正しく参加していませんか?

4

1 に答える 1