【问题标题】:implementing python multithreading into the calculation for a progressbar在进度条的计算中实现 python 多线程
【发布时间】:2020-11-17 16:52:53
【问题描述】:

我正在尝试创建一个类似于tqdm 的进度条。一切正常,但我注意到进度条每一步的计算(对于大迭代,len > 50)需要很多时间。这是我的代码。

def progressbar(iterable):
    def new(index):
        #... print the progressbar
    for i in range(len(iterable)):
        new(i)
        yield iterable[i]

问题在于,虽然在小型迭代器上new() 执行的时间无关紧要,但在较大的迭代器上它会成为一个问题(这在 tqdm 库中不会发生)。例如,以下代码需要几秒钟才能执行。应该是即时的!

iterator = progressbar(range(1000))
for i in iterator: pass

你能告诉我解决这个问题的方法吗?也许实现多线程?

【问题讨论】:

  • 在 python3.8 上,你最后一个带有 range(100000) 的 sn-p 需要 0.11 秒才能运行。您是否通过分析器运行代码?
  • 使整个事情变慢的是新功能,即计算百分比等...我发现执行需要 0.001 秒。在小的迭代器上这不是问题,但是当它们变得大于 100 时,它就是 @imochoa

标签: python python-3.x multithreading progress-bar


【解决方案1】:

目前尚不清楚问题是什么(您没有显示所有计算),但我相信您的方法可以通过您的进度条处理 iterable 的方式得到改进:

  1. 首先,您假设iterable 是可索引的,但情​​况并非总是如此。
  2. 如果它是一个生成器函数,那么长度可能无法使用len 函数确定,也不会将生成器转换为列表以获得其长度必然是有效的,并且它可能会破坏具有进度条的目的,如下例所示。因此,您的界面应允许用户传递可选的total 参数(如tqdm 所做的那样)以明确指定iterable 的长度。
  3. 您可以在函数new 之外进行一些前期计算,以便new 可以根据index 参数的值快速计算条形应该有多宽。

我建议进行以下更改:

def progressbar(iterable, total=None):
    def new(index):
        #... print the progressbar
        from math import floor
        nonlocal division, width
        n_division = floor(index / division + .5)
        remainder = width - n_division
        print('|', '.' * n_division, ' ' * remainder, '|', sep='', end='\r')

    if total is None:
        iterable = list(iterable)
        # we must convert to a list
        total = len(iterable)
    it = iter(iterable)

    width = 60 # with of progress bar
    division = total / 60 # each division represents this many completions

    try:
        for i in range(total):
            # ensure next value exists before printing it:
            yield next(it)
            new(i)
    except StopIteration:
        pass
    print()

def fun():
    import time
    for i in range(1000):
        time.sleep(.03)
        yield i

iterator = progressbar(fun(), total=1000)
values = [i for i in iterator]
print(values[0], values[-1])

多线程

将多线程作为加速处理的一种方式是有问题的。以下是这样做的(天真)尝试失败,因为尽管使用多线程从生成器函数fun 获取值,但生成器函数仍然每 0.03 秒仅生成一次值。还应该清楚的是,例如,如果iterable 是一个简单的列表,那么多线程将无法比使用单线程更快地迭代列表:

from multiprocessing.pool import ThreadPool


def progressbar(iterable, total=None):
    def new(index):
        #... print the progressbar
        from math import floor
        nonlocal division, width
        n_division = floor(index / division + .5)
        remainder = width - n_division
        print('|', '.' * n_division, ' ' * remainder, '|', sep='', end='\r')
    if total is None:
        iterable = list(iterable)
        # we must convert to a list
        total = len(iterable)
    it = iter(iterable)

    width = 60 # with of progress bar
    division = total / 60 # each division represents this many completions
    
    with ThreadPool(20) as pool:
        for i, result in enumerate(pool.imap(lambda x: x, iterable)):
            yield result
            new(i)
        print()

def fun():
    import time
    for i in range(1000):
        time.sleep(.03)
        yield i

iterator = progressbar(fun(), total=1000)
values = [i for i in iterator]
print(values[0], values[-1])

如果生成器函数本身使用了多线程,将会加快处理速度。但是,当然,无法控制iterable 的创建方式:

from multiprocessing.pool import ThreadPool


def progressbar(iterable, total=None):
    def new(index):
        #... print the progressbar
        from math import floor
        nonlocal division, width
        n_division = floor(index / division + .5)
        remainder = width - n_division
        print('|', '.' * n_division, ' ' * remainder, '|', sep='', end='\r')

    if total is None:
        iterable = list(iterable)
        # we must convert to a list
        total = len(iterable)
    it = iter(iterable)

    width = 60 # with of progress bar
    division = total / 60 # each division represents this many completions

    try:
        for i in range(total):
            # ensure next value exists before printing it:
            yield next(it)
            new(i)
    except StopIteration:
        pass
    print()


def fun():
    import time

    def fun2(i):
        time.sleep(.03)
        return i

    with ThreadPool(20) as pool:
        for i in pool.imap(fun2, range(1000)):
            yield i

iterator = progressbar(fun(), total=1000)
values = [i for i in iterator]
print(values[0], values[-1])

【讨论】:

  • 听起来很有趣,显然事情没有那么简单,我还添加了估计时间等...但这是一个非常好的起点,谢谢,我会努力工作然后我会告诉你的!
  • 我将考虑添加多线程。
  • 这绝对是我一直在寻找的答案,谢谢!我会接受这个答案,并在系统允许的情况下首先给予你的赏金!
  • 问题实际上不是我想的那个问题,我创建了一个可以替代“print()”的函数(添加了一些功能),但发现它真的很慢。使用 print 它工作顺利。但是我发现使用“next()”的过程真的很有趣。
猜你喜欢
  • 1970-01-01
  • 2011-06-21
  • 1970-01-01
  • 1970-01-01
  • 2014-01-06
  • 1970-01-01
  • 2014-10-06
  • 1970-01-01
  • 2020-10-29
相关资源
最近更新 更多