一旦他们开始,你如何杀死期货?

时间:2015-03-20 23:42:07

标签: python multithreading concurrent.futures

我正在使用新的concurrent.futures模块(也有一个Python 2 backport)来做一些简单的多线程I / O.我无法理解如何彻底杀死使用此模块开始的任务。

查看以下Python 2/3脚本,它重现了我所看到的行为:

#!/usr/bin/env python
from __future__ import print_function

import concurrent.futures
import time


def control_c_this():
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
        future1 = executor.submit(wait_a_bit, name="Jack")
        future2 = executor.submit(wait_a_bit, name="Jill")

        for future in concurrent.futures.as_completed([future1, future2]):
            future.result()

        print("All done!")


def wait_a_bit(name):
    print("{n} is waiting...".format(n=name))
    time.sleep(100)


if __name__ == "__main__":
    control_c_this()

当这个脚本运行时,似乎无法使用常规的Control-C键盘中断干净地杀死它。我在OS X上运行。

  • 在Python 2.7上,我不得不从命令行求助kill来杀死脚本。 Control-C被忽略了。
  • 在Python 3.4上,如果你点击两次,Control-C就可以工作,但是会抛弃很多奇怪的堆栈跟踪。

我在网上找到的大多数文档都讨论了如何使用旧的threading模块彻底杀死线程。这似乎都不适用于此。

concurrent.futures模块中提供的所有停止内容的方法(如Executor.shutdown()Future.cancel())仅在期货尚未开始或尚未完成时才有效,这是毫无意义的在这种情况下。我想立即打断未来。

我的用例很简单:当用户点击Control-C时,脚本应该像任何行为良好的脚本一样立即退出。这就是我想要的。

那么在使用concurrent.futures时获得此行为的正确方法是什么?

2 个答案:

答案 0 :(得分:16)

这有点痛苦。本质上,您的工作线程必须在主线程退出之前完成。你不能退出,除非他们这样做。典型的解决方法是拥有一些全局状态,每个线程都可以检查以确定它们是否应该执行更多工作。

这是解释原因的quote。实质上,如果线程在解释器执行时退出,则可能发生不好的事情。

这是一个有效的例子。请注意,由于子线程的睡眠持续时间,C-c最多需要1秒才能传播。

#!/usr/bin/env python
from __future__ import print_function

import concurrent.futures
import time
import sys

quit = False
def wait_a_bit(name):
    while not quit:
        print("{n} is doing work...".format(n=name))
        time.sleep(1)

def setup():
    executor = concurrent.futures.ThreadPoolExecutor(max_workers=5)
    future1 = executor.submit(wait_a_bit, "Jack")
    future2 = executor.submit(wait_a_bit, "Jill")

    # main thread must be doing "work" to be able to catch a Ctrl+C 
    # http://www.luke.maurits.id.au/blog/post/threads-and-signals-in-python.html
    while (not (future1.done() and future2.done())):
        time.sleep(1)

if __name__ == "__main__":
    try:
        setup()
    except KeyboardInterrupt:
        quit = True

答案 1 :(得分:1)

我遇到了这个问题,但是我遇到的问题是,许多期货(成千上万的期货)将在等待运行,仅按Ctrl-C可使它们等待,而实际上并未退出。我当时使用concurrent.futures.wait运行进度循环,需要添加try ... except KeyboardInterrupt来处理未完成的期货取消。

POLL_INTERVAL = 5
with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_WORKERS) as pool:
    futures = [pool.submit(do_work, arg) for arg in large_set_to_do_work_over]
    # next line returns instantly
    done, not_done = concurrent.futures.wait(futures, timeout=0)
    try:
        while not_done:
            # next line 'sleeps' this main thread, letting the thread pool run
            freshly_done, not_done = concurrent.futures.wait(not_done, timeout=POLL_INTERVAL)
            done |= freshly_done
            # more polling stats calculated here and printed every POLL_INTERVAL seconds...
    except KeyboardInterrupt:
        # only futures that are not done will prevent exiting
        for future in not_done:
            # cancel() returns False if it's already done or currently running,
            # and True if was able to cancel it; we don't need that return value
            _ = future.cancel()
         # wait for running futures that the above for loop couldn't cancel (note timeout)
         _ = concurrent.futures.wait(not_done, timeout=None)

如果您对保持准确了解已完成和未完成的内容(即不希望出现进度循环)感兴趣,则可以替换第一个等待调用(一个timeout=0)和not_done = futures并仍然保留while not_done:逻辑。

for future in not_done:取消循环可能会根据返回值(或写为理解值)而有所不同,但是等待期货完成或取消并不是真正的等待-它会立即返回。最后waittimeout=None确保池的运行作业确实完成。

同样,仅当实际调用的do_work最终在合理的时间内返回时,此方法才能正确运行。这对我来说很好-实际上,我想确保如果do_work开始,它将运行到完成。如果do_work是“无尽的”,那么您将需要类似cdosborn的答案,该答案使用对所有线程可见的变量,以指示它们停止运行。