【问题标题】:Waiting for specific threads in list等待列表中的特定线程
【发布时间】:2018-07-26 20:47:21
【问题描述】:

我有 3 个线程:A、B 和 C。

它们在 threads 列表中,并与

同时触发
A.start()
B.start()
C.start()

我不知道哪个线程会先完成。

我需要在每个线程完成时触发它的动作,就像这样:

for t in threads:
    t.join()
    action()

此代码在这种情况下不正确,因为:我必须等待第一个 A 线程在检查 B 之前完成,并等待 B 完成以检查 C 等等。 .

如何以独立的方式等待每个线程,好像“.join()”是一个事件?

【问题讨论】:

  • 如何创建线程?
  • 不需要join。只需在循环中检查每个is_alive,直到其中一个说不是。
  • 非常感谢

标签: python python-3.x multithreading events


【解决方案1】:

您的问题遗漏了许多细节,所以我猜测并提出以下方法来演示一种方法:

from random import random
from threading import Thread
from time import sleep

def callback(name):
    print('thread {} finished'.format(name))

A = Thread(target=lambda: sleep(random()))
B = Thread(target=lambda: sleep(random()))
C = Thread(target=lambda: sleep(random()))

A.start()
B.start()
C.start()

threads = {'A': A, 'B': B, 'C': C}
while threads:
    for name, thread in sorted(threads.items()):
        if not thread.is_alive():
            callback(name)
            threads.pop(name)
            break

print('done')

典型输出:

thread C finished
thread A finished
thread B finished
done

【讨论】:

  • 这是个坏主意。您的主线程正在尝试使用 100% CPU 忙轮询线程。如果他们什么都不做,这意味着你的 CPU 是 100%,而不是 1%。如果他们确实需要做任何事情,这意味着你正在从他们那里窃取 GIL。 (即使它们主要是带有一点 CPU 的 I/O,让四个线程而不是三个线程争夺 GIL 也会产生重大影响。)
  • @abarnert:由于您提到的原因,通常我不会编写这样的忙等待循环。但是,在这种情况下,我这样做了,因为没有其他详细信息,我想“为什么不呢?”。如果锤击 CPU 是个问题,可以简单地将 time.sleep(.001) 语句放在内部或外部循环中以短暂释放它。
【解决方案2】:

您正在寻找的是一种在线程组上等待的方法,而 Python 的 API 没有提供这种方法。所以,你必须模拟它。


最简单但效率最低的方法就是忙轮询线程:

while threads:
    for thread in threads:
        thread.join(timeout=0.1)
        if not thread.is_alive():
            action(thread)
            threads.remove(thread)
            break
    time.sleep(0.1)

更好的解决方案是使用一个同步对象,所有线程都可以在它们退出之前发出信号。你可以用一个简单的Event

while threads:
    evt.wait()
    evt.clear()
    for thread in threads:
        if not thread.is_alive():
            thread.join()
            action(thread)
            threads.remove(thread)
            break

但是,这与任何自重置事件具有相同的潜在竞争条件:您可能会错过触发器。在某些情况下,您可以仔细研究所有细节并确定比赛不会造成任何伤害。如果没有,您必须使用稍微复杂一些的东西,例如 Condition


或者您可以跳到更高的级别并使用Queue,线程可以在退出之前使用q.put(self)。那么主线程就容易搞定了:

while threads:
    thread = q.get()
    thread.join()
    action(thread)
    threads.remove(thread)

但值得考虑的是,您是否真的需要 3 个线程函数,或者 3 个可以在池或执行器上运行的任务函数,或者甚至是 1 个具有 3 个不同参数的任务函数。

例如,对于具有 3 组 args 的单个函数,很难比这更简单:

with multiprocessing.dummy.Pool(3) as pool:
    for result in pool.imap_unordered(func, [arg1, arg2, arg3]):
        action(result)

或者,对于 3 个功能:

with concurrent.futures.ThreadPoolExecutor(max_workers=3) as ex:
    fs = [ex.submit(func) for func in funcs]
    for f in concurrent.futures.as_completed(fs):
        action(f.result())

或者您甚至可以将action 添加到期货上:

with concurrent.futures.ThreadPoolExecutor(max_workers=3) as ex:
    for func in funcs:
        ex.submit(func).add_done_callback(action)

【讨论】:

  • 在我看来,您建议的许多代码循环都会改变被迭代的对象,我相信您知道这可能会导致问题。
  • @martineau 我在其中一个中缺少break;修复了它,(尽管迭代副本而不是breaking 可能更清楚?)谢谢。其他的,我没有改变被迭代的对象;我只是对while obj: 循环中使用的对象进行变异,这很好。
猜你喜欢
  • 2012-04-14
  • 1970-01-01
  • 1970-01-01
  • 2019-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多