【问题标题】:Pause and resume thread in pythonpython中的暂停和恢复线程
【发布时间】:2017-06-14 18:04:47
【问题描述】:

我需要暂停和恢复线程,它不断地执行一些任务。执行在调用start() 时开始,不应中断,必须从调用pause() 时继续执行。

我该怎么做?

【问题讨论】:

标签: python multithreading


【解决方案1】:

请记住,在 Python 中使用线程不会授予您并行处理,除非是 IO 阻塞操作。有关这方面的更多信息,请查看 thisthis

您不能在 Python 中任意暂停线程(在进一步阅读之前请记住这一点)。我不确定您是否有办法在操作系统级别执行此操作(例如,通过使用纯 C)。您可以做的是让线程在您事先考虑的特定点暂停。我举个例子:

class MyThread(threading.Thread):

    def __init__(self, *args, **kwargs):
        super(MyThread, self).__init__(*args, **kwargs)
        self._event = threading.Event()

    def run(self):
        while True:
            self.foo() # please, implement this.
            self._event.wait()
            self.bar() # please, implement this.
            self._event.wait()
            self.baz() # please, implement this.
            self._event.wait()

    def pause(self):
        self._event.clear()

    def resume(self):
        self._event.set()

这种方法可行,但是:

  • 根据我给你的链接,线程通常是个坏主意。
  • 您必须自己编写 run 方法,使用这种方法。这是因为您需要控制要检查暂停的确切点,这意味着访问 Thread 对象(也许您想创建一个额外的方法而不是调用 self._event.wait())。
  • 前一点清楚地表明您不能随意暂停,但只有在您指定时才可以暂停。避免在暂停点之间进行长时间的操作。

编辑我没有测试过这个,但如果你需要多个这样的线程,也许不需要太多子类化就可以工作:

class MyPausableThread(threading.Thread):

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
        self._event = threading.Event()
        if target:
            args = (self,) + args
        super(MyPausableThread, self).__init__(group, target, name, args, kwargs)

    def pause(self):
        self._event.clear()

    def resume(self):
        self._event.set()

    def _wait_if_paused(self):
        self._event.wait()

这应该允许您通过调用MyPausableThread(target=myfunc).start() 创建一个无需更多子类化的自定义线程,并且您的可调用对象的第一个参数将接收线程对象,当您需要暂停检查时可以从中调用self._wait_if_paused()

或者甚至更好,如果你想隔离目标访问线程对象:

class MyPausableThread(threading.Thread):

    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
        self._event = threading.Event()
        if target:
            args = ((lambda: self._event.wait()),) + args
        super(MyPausableThread, self).__init__(group, target, name, args, kwargs)

    def pause(self):
        self._event.clear()

    def resume(self):
        self._event.set()

您的目标可调用对象将在第一个参数中接收一个可以像这样调用的函数:pause_checker()(前提是目标可调用对象中的第一个参数名为 pause_checker)。

【讨论】:

  • 如果你不介意我问,为什么线程是一个坏主意?它的实现通常很糟糕(就像在Stop Writing Classes 中很好地解释的那样),但它对于解决并发问题(尤其是对于慢速 I/O 操作)和其他所有事情来说都是多处理的好主意。您正确地介绍了您的答案,但仅仅因为您可以多进程并不意味着当您需要并行化以用于处理优化以外的目的时,线程不是一个好主意。
  • 好吧,对于不涉及 I/O 的情况,最好使用异步处理,例如龙卷风可以。这可以为您节省大量的上下文切换和类似的开销,因为它是一个非线程范例(关于“暂停”的提示相同!)。但是,如果生成少量线程,则不会注意到差异。
  • 顺便说一句,我添加了只添加一个新类的用例。但是对于要暂停的线程,您需要在实现和容器(线程)之间进行某种通信。所以我添加了一种方法(只需要一个可重用的子类),可以帮助您控制暂停。
  • asyncio 在 Python 3.4 之前的版本中不存在,它本质上以更用户友好的方式使用相同的线程接口(具有可接受的开销),而 Tornado 是第三个第三方库 - 不需要第三方库来有效地使用 I/O 等语言的基本功能。此外,您绝对可以通过阻止 GIL 进行上下文切换来暂停线程。
  • @LuisMasuelli:这是个坏主意,但你可以在 python 中任意暂停线程。看我的回答。
【解决方案2】:

您可以通过附加一个导致所有其他线程等待信号的跟踪函数来做到这一点:

import sys
import threading
import contextlib

# needed to enable tracing
if not sys.gettrace():
    sys.settrace(lambda *args: None)

def _thread_frames(thread):
    for thread_id, frame in sys._current_frames().items():
        if thread_id == thread.ident:
            break
    else:
        raise ValueError("No thread found")
    # walk up to the root
    while frame:
        yield frame
        frame = frame.f_back


@contextlib.contextmanager
def thread_paused(thread):
    """ Context manager that pauses a thread for its duration """
    # signal for the thread to wait on
    e = threading.Event()

    for frame in _thread_frames(thread):
        # attach a new temporary trace handler that pauses the thread

        def new(frame, event, arg, old = frame.f_trace):
            e.wait()

            # call the old one, to keep debuggers working
            if old is not None:
                return old(frame, event, arg)
        frame.f_trace = new

    try:
        yield
    finally:
        # wake the other thread
        e.set()

你可以用作:

import time

def run_after_delay(func, delay):
    """ Simple helper spawning a thread that runs a function in the future """
    def wrapped():
        time.sleep(delay)
        func()
    threading.Thread(target=wrapped).start()

main_thread = threading.current_thread()

def interrupt():
    with thread_paused(main_thread):
        print("interrupting")
        time.sleep(2)
        print("done")

run_after_delay(interrupt, 1)
start = time.time()
def actual_time(): return time.time() - start

print("{:.1f} == {:.1f}".format(0.0, actual_time()))
time.sleep(0.5)
print("{:.1f} == {:.1f}".format(0.5, actual_time()))
time.sleep(2)
print("{:.1f} != {:.1f}".format(2.5, actual_time()))

给予

0.0 0.0
0.5 0.5
interrupting
done
2.5 3.0

注意中断如何导致主线程上的睡眠等待更长的时间

【讨论】:

  • 这是一个很棒的技巧! (一个伟大的黑客我永远不会使用,但无论如何)。刚刚+1。
【解决方案3】:

您可以使用 psutil 库中的 Process 类来执行此操作。

例子:

>>> import psutil
>>> pid = 7012
>>> p = psutil.Process(pid)
>>> p.suspend()
>>> p.resume()

看到这个答案:https://stackoverflow.com/a/14053933

编辑:此方法将暂停整个进程,而不仅仅是一个线程。 (我不删除这个答案,所以其他人可以知道这个方法行不通。)

【讨论】:

    【解决方案4】:
    while(int(any) < 2000):
                 sleep(20)
                 print(waiting any...)
    

    【讨论】:

    • 一般来说,如果答案包含对代码的用途的解释,以及为什么在不介绍其他人的情况下解决问题的原因,答案会更有帮助。
    猜你喜欢
    • 1970-01-01
    • 2016-03-24
    • 1970-01-01
    • 2010-12-28
    • 2011-08-08
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    相关资源
    最近更新 更多