2016-10-23 10 views
2

私は、進捗バーなどの並列進行情報で印刷する必要のある簡単なプロジェクトを持っています。並列印刷と書き込みロック

各バーには位置があり、バーの位置に応じて端末の書き込みカーソルが上下に移動します。

これはシリアルで実行するとうまく動作しますが、レースの問題で並行して印刷すると失敗します。私はmultiprocessing.Lock()を使用しようとしましたが、無駄です。ここで

は私の現在のコードです:

from __future__ import division 

import os, sys 
import signal 
from time import sleep 
from multiprocessing import Pool, freeze_support, Lock 

if os.name == 'nt': 
    import colorama # to support cursor up 
    colorama.init() 

_term_move_up = '\x1b[A' 

write_lock = Lock() 

class simple_bar(object): 
    def __init__(self, iterable, desc='', position=0): 
     signal.signal(signal.SIGINT, signal.SIG_IGN) # handles keyboardinterrupt 
     self.iterable = iterable 
     self.total = len(iterable) 
     self.n = 0 
     self.position = position 
     self.desc = desc 
     self.display() 

    def __iter__(self): 
     for obj in self.iterable: 
      yield obj 
      self.update() 

    def update(self, n=1): 
     self.n += n 
     self.display() 

    def display(self, fp=None, width=79): 
     if not fp: 
      fp = sys.stdout 

     with write_lock: 
      fp.write('\n' * self.position) 
      l_part = self.desc + ': ' 
      bar = l_part + '#' * int((self.n/self.total) * (width - len(l_part))) 
      fp.write('\r' + bar + ' ' * (width - len(bar))) 
      fp.write(_term_move_up * self.position) 
      fp.flush() 

def progresser(n):   
    text = "progresser #{}".format(n) 
    for i in simple_bar(range(5000), desc=text, position=n): 
     sleep(0.001) 

if __name__ == '__main__': 
    freeze_support() 
    L = list(range(3)) 
    Pool(len(L)).map(progresser, L) 

OK働くシリアル代替、これは上記の並列バージョンによって生成されなければならない正しい出力できます:

# Same code as above, except __main__ 

if __name__ == '__main__': 
    t_list = [simple_bar(range(5000), desc="progresser #{}".format(n), position=n) for n in xrange(3)] 
    for i in range(5000): 
     for t in t_list: 
      t.update() 

を私は何見当がつかないうまくいかない。私は無事マルチプロセッシングに並列に印刷する方法を探して、理想的にはしかし場合スレッド安全

のWindows 7上のPython 2.7.12を使用しています。

/編集:私はちょうど印刷前の待機(しかし、十分な大きさ)を入れた場合、興味深いことに、その後、バーが無事に印刷されています。私はこれが意味するものを結論を知らない

# ... 
    def display(self, fp=None, width=79): 
     if not fp: 
      fp = sys.stdout 

     with write_lock: 
      sleep(1) # this fixes the issue by adding a delay 
      fp.write('\n' * self.position) 
      l_part = self.desc + ': ' 
      bar = l_part + '#' * int((self.n/self.total) * (width - len(l_part))) 
      fp.write('\r' + bar + ' ' * (width - len(bar))) 
      fp.write(_term_move_up * self.position) 
      fp.flush() 
# ... 

+0

がわからない私が正しく理解して、このコードを試してみてくださいようです。一部のジョブを同時に処理し、その一部が完了したときにプログレスバーを印刷しますか?進行状況があなたのサブプロセスかあなたのメインプロセスによって印刷されるかどうかは重要ですか? – noxdafox

+0

@noxdafoxはい、最初の質問には、2番目のはい、進捗状況は、サブプロセスから印刷する必要があります、これは問題です。メインプロセスからは、並行性がないため問題はありません。 – gaborous

答えて

1

グローバルロック変数に問題がある可能性があります。 UNIXプロセスで子プロセスを作成する場合は、親プロセスのメモリのコピーがあります。 Windowsではない場合

from __future__ import division 
import os, sys 
import signal 
from time import sleep 
from multiprocessing import Pool, freeze_support, Lock 

if os.name == 'nt': 
    import colorama # to support cursor up 
    colorama.init() 

_term_move_up = '\x1b[A' 



class simple_bar(object): 
    def __init__(self, iterable, desc='', position=0): 
     signal.signal(signal.SIGINT, signal.SIG_IGN) # handles keyboardinterrupt 
     self.iterable = iterable 
     self.total = len(iterable) 
     self.n = 0 
     self.position = position 
     self.desc = desc 
     self.display() 

    def __iter__(self): 
     for obj in self.iterable: 
      yield obj 
      self.update() 

    def update(self, n=1): 
     self.n += n 
     self.display() 

    def display(self, fp=None, width=79): 
     if not fp: 
      fp = sys.stdout 

     with write_lock: 
      fp.write('\n' * self.position) 
      l_part = self.desc + ': ' 
      bar = l_part + '#' * int((self.n/self.total) * (width - len(l_part))) 
      fp.write('\r' + bar + ' ' * (width - len(bar))) 
      fp.write(_term_move_up * self.position) 
      fp.flush() 

def progresser(n): 
    text = "progresser #{}".format(n) 
    for i in simple_bar(range(5000), desc=text, position=n): 
     sleep(0.001) 

def init_child(lock_): 
    global write_lock 
    write_lock = lock_ 

if __name__ == '__main__': 
    write_lock = Lock() 
    L = list(range(3)) 
    pool = Pool(len(L), initializer=init_child, initargs=(write_lock,)) 
    pool.map(progresser, L) 
+0

良いキャッチ、それは動作します。でも、理想的には、親がロックを提供する必要はありません(ロックは子供が作成する必要があります、クラスによって、または私は何を知らないのですか)。これが可能だと思いますか? – gaborous

+0

Alexeyは言っているように、Windowsはプロセスをフォークすることはできませんが、子プロセスは親プロセスのデータにアクセスできません。親から子供にロックを渡す必要があります。参照:http://stackoverflow.com/a/28721419およびhttp://rhodesmill.org/brandon/2010/python-multiprocessing-linux-windows/ – gaborous

4

write_lock.release()の前にfp.flush()を追加する必要があります。

無関係コメント:

  • コンテキストマネージャ(代わりに手動acquire()release()with write_lock...)としてロックの使用を検討 - 従う少ないエラーが発生しやすいが容易です。
  • いずれのバージョンでも割り込み(Ctrl + C)を処理しません。そのことを調べることができます。
+0

提案していただきありがとうございます。申し訳ありませんが、これで問題は解決されません。上記の更新されたコードをご覧ください。私の元のコードでは、fp.flush()が呼び出されましたが、このコンパクト化されたバージョンで追加するのを忘れました。申し訳ありませんが、とにかく役に立ちません。 – gaborous

関連する問題