2016-10-23 3 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) 

Последовательная альтернатива, которая работает хорошо, это дает правильный вывод, который должен быть получен в параллельной версии выше:

# 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() 

Я понятия не имею, что идет не так. Я использую Python 2.7.12 на Windows 7.

Ищу способ печати параллельно безопасно в многопроцессорных и идеально, но необязательно резьбы безопасно.

/EDIT: интересно, если я поставлю ждать (но достаточно большая) непосредственно перед печатью, то бруски печатаются в порядке:

# ... 
    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 Да к первому вопросу, для второго да, прогресс должен быть напечатан из подпроцесса, это проблема. Из основного процесса нет проблемы, так как нет параллелизма. – gaborous

ответ

1

Это может быть проблема с глобальной переменной блокировки. Когда вы создаете дочерний процесс в unix, у вас есть копия памяти родителя. В окнах, кажется, что это не так

Попробуйте этот код

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

Хороший улов, он работает. Damn Windows ... Однако в идеале я хотел бы управлять блокировкой прозрачно для родителя, не требуя, чтобы родитель предоставил блокировку (блокировка должна быть создана дочерними элементами, может быть, классом или я не знаю, что).Считаете ли вы, что это возможно? – gaborous

+0

Никоим образом, как сказал Алексей, Windows не может обрабатывать процессы, но только порождает их, поэтому дочерний процесс не имеет доступа к данным родителя. Нам нужно передать замок от родителя к детям. См. Также: http://stackoverflow.com/a/28721419 и http://rhodesmill.org/brandon/2010/python-multiprocessing-linux-windows/ – gaborous

4

Необходимо добавить fp.flush() до write_lock.release().

несвязанные комментарии:

  • Рассмотрим используя замок в качестве менеджера контекста (with write_lock... вместо ручного acquire() и release()) - что легче следовать, и менее подвержены ошибкам.
  • Ни одна из версий не обрабатывает прерывания (Ctrl + C), вы можете захотеть изучить это.
+0

Благодарим вас за ваши предложения, но, к сожалению, это не устраняет проблему, см. Обновленный код выше. В моем исходном коде был вызван fp.flush(), я забыл добавить его в эту версию, извините, но в любом случае это не поможет. – gaborous

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