簡體   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