繁体   English   中英

Python 睡眠而不阻塞其他进程

[英]Python sleep without blocking other processes

我每小时运行一个 python 脚本,并且我一直在 while 循环中使用time.sleep(3600) 它似乎可以根据需要工作,但我担心它会阻止新任务。 我对此的研究似乎是它只阻塞了当前线程,但我想 100% 确定。 虽然每小时的工作不应该超过 15 分钟,但如果它完成或挂起,我不希望它阻止下一个开始的工作。 这就是我的做法:

import threading
import time


def long_hourly_job():
    # do some long task
    pass


if __name__ == "__main__":
    while True:
        thr = threading.Thread(target=long_hourly_job)
        thr.start()
        time.sleep(3600)

这足够了吗?

此外,我将time.sleep用于这个每小时工作而不是 cron 工作的原因是我想在代码中做所有事情以使 dockerization 更清洁。

该代码将起作用(即: sleep只会阻塞调用线程),但您应该注意一些问题。 其中一些已经在评论中说明,例如线程之间时间重叠的可能性。 主要问题是您的代码正在慢慢泄漏资源。 创建线程后,操作系统会保留一些数据结构,即使在线程完成运行后也是如此。 这是必要的,例如保持线程的退出状态直到线程的创建者需要它。 清除这些结构(概念上相当于关闭文件)的 function 称为join 已完成运行且未join的线程称为“僵尸线程”。 这些结构所需的 memory 的数量非常少,并且对于任何合理数量的可用 RAM,您的程序应该运行几个世纪。 不过,加入您创建的所有线程是一个很好的做法。 一个简单的方法(如果您知道 3600 秒足以让线程完成)是:

if __name__ == "__main__":
    while True:
        thr = threading.Thread(target=long_hourly_job)
        thr.start()
        thr.join(3600)  # wait at most 3600 s for the thread to finish
        if thr.isAlive(): # join does not return useful information
            print("Ooops: the last job did not finish on time")

如果您认为有时 3600 秒可能不足以让线程完成,则可以使用更好的方法:

if __name__ == "__main__":
    previous = []
    while True:
        thr = threading.Thread(target=long_hourly_job)
        thr.start()
        previous.append(thr)
        time.sleep(3600)
        for i in reversed(range(len(previous))):
            t = previous[i]
            t.join(0)
            if t.isAlive():
                print("Ooops: thread still running")
            else:
                print("Thread finished")
                previous.remove(t)

我知道print语句没有意义:改用logging

也许有点晚了。 我从其他答案中测试了代码,但我的主要过程卡住了(也许我做错了什么?)。 然后我尝试了一种不同的方法来模拟 QtCore.QTimer() 行为。 我认为它不是非常精确,它仍然包含一个 time.sleep() 语句,但要短得多,因此它不会阻塞主进程(至少不显着):

import threading
import time
import traceback


class Timer:
    SNOOZE = 0
    ONEOFF = 1

    def __init__(self, timerType=SNOOZE):

        self._obj = self._Counter()
        self._kill = threading.Event()
        self._timerType = timerType
        self._msec = 0
        self._thread = None
        self._function = None

    class _Counter:

        def __init__(self):
            super().__init__()

        def run(self, quit_event: threading.Event(), msec: int, callback):
            endTime = round(time.time() * 1000) + msec
            while round(time.time() * 1000) < endTime and not quit_event.is_set():
                time.sleep(0.001)
            if not quit_event.is_set():
                callback()

    def start(self, msec, callback):
        self._function = callback
        self._msec = msec
        if self._function and hasattr(self._function, '__call__') and msec > 0:
            self._thread = threading.Thread(target=self._obj.run, args=(self._kill, self._msec, self._callback))
            self._thread.setDaemon(True)
            self._thread.start()

    def _callback(self):
        try:
            self._function()
            if self._timerType == self.SNOOZE:
                self.start(self._msec, self._function)
        except:
            tb_content = traceback.format_exc()
            print(tb_content)
            self.stop()

    def stop(self):
        self._kill.set()
        self._thread.join()


KEEP = True


def callback():
    global KEEP
    KEEP = False
    print("ENDED", time.strftime("%M:%S"))


if __name__ == "__main__":
    count = 0
    t = Timer(timerType=Timer.ONEOFF)
    t.start(5000, callback)
    print("START", time.strftime("%M:%S"))
    while KEEP:
        if count % 10000000 == 0:
            print("STILL RUNNING")
        count += 1

注意 while 循环和 time.sleep() 语句在单独的线程中运行,使用 time.time() 测量经过的时间,并使用回调 function 在时间结束时调用(在您的情况下,此回调function 将用于检查长时间运行的进程是否已完成)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM