【问题标题】:Parallel printing and write lock并行打印和写锁
【发布时间】:2017-03-05 14:20:32
【问题描述】:

我有一个简单的项目,我需要并行打印进度信息,例如进度条。

每个栏都有一个位置,终端中的书写光标根据栏的位置上下移动。

这在串行完成时效果很好,但在并行打印时由于赛车问题而失败。我尝试使用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()

我不知道出了什么问题。我在 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()
# ...

我不知道这意味着什么结论。

【问题讨论】:

  • 不确定我理解正确。您是否要同时处理某些作业并在部分完成时打印进度条?进度是由您的子流程还是由您的主流程打印是否重要?
  • @noxdafox 第一个问题是,第二个是,应该从子进程打印进度,这就是问题所在。从主进程来看,没有任何问题,因为不涉及并发。

标签: python multithreading multiprocessing race-condition


【解决方案1】:

您需要在write_lock.release()之前添加fp.flush()

不相关的 cmets:

  • 考虑将锁用作上下文管理器(with write_lock... 而不是手动的acquire()release())——这样更容易理解且不易出错。
  • 这两个版本都不能很好地处理中断 (Ctrl+C),您可能需要研究一下。

【讨论】:

  • 感谢您的建议,但很抱歉这并不能解决问题,请参阅上面的更新代码。在我的原始代码中,调用了 fp.flush(),我忘记在这个压缩版本中添加它,对此感到抱歉,但无论如何它没有帮助。
【解决方案2】:

这可能是全局锁变量的问题。当您在 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)

【讨论】:

  • 很好,它有效。该死的Windows...但是理想情况下,我希望为父级透明地管理锁,而不需要父级提供锁(锁应该由孩子创建,可能由班级创建,或者我不知道是什么)。你认为这可能吗?
  • 没办法,正如 Alexey 所说,Windows 不能派生进程,只能派生它们,因此子进程无法访问父进程的数据。我们需要将锁从父级传递给子级。另见:stackoverflow.com/a/28721419rhodesmill.org/brandon/2010/…
猜你喜欢
  • 1970-01-01
  • 2019-12-20
  • 1970-01-01
  • 1970-01-01
  • 2020-03-21
  • 2011-12-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多