乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Python | 優(yōu)雅地實(shí)現(xiàn)等待

       禁忌石 2023-02-16 發(fā)布于浙江

      對(duì)于許多類(lèi)型的應(yīng)用程序,有時(shí)需要暫停程序的運(yùn)行,直到出現(xiàn)某些外部條件??赡苄枰鹊搅硪粋€(gè)線程完成,或者可能需要等到正在監(jiān)視的磁盤(pán)目錄中出現(xiàn)新文件。

      在這種情況下,需要找出一種方法叫腳本等待。正確的操作并不像聽(tīng)起來(lái)那么容易!

      在本文中,我將向你展示幾種不同的等待方式。所有的示例都將使用Python,但我將要介紹的概念適用于所有編程語(yǔ)言。

      一個(gè)典型示例

      為了向你展示這些等待模式,請(qǐng)看下面的示例:

      from random import randomimport threadingimport timeresult = Nonedef background_calculation(): # here goes some long calculation time.sleep(random() * 5 * 60) # when the calculation is done, the result is stored in a global variable global result result = 42def main(): thread = threading.Thread(target=background_calculation) thread.start() # TODO: wait here for the result to be available before continuing! print('The result is', result)if __name__ == '__main__': main()

      程序中background_calculation()函數(shù)執(zhí)行一些速度較慢的計(jì)算。為了使這個(gè)例子更簡(jiǎn)單,我在函數(shù)中使用了time.sleep()調(diào)用一個(gè)隨機(jī)值,隨機(jī)值最長(zhǎng)為5分鐘。最后,全局變量result的值為42。

      主程序函數(shù)在單獨(dú)的線程中啟動(dòng)后臺(tái)計(jì)算,然后等待線程完成其工作,最后打印全局變量的值。這個(gè)版本函數(shù)沒(méi)有實(shí)現(xiàn)等待,你可以在需要等待的地方看到一個(gè)TODO注釋。

      接下來(lái),我將向你展示一些不同的方法來(lái)實(shí)現(xiàn)此處的等待,從最壞的開(kāi)始,以我的方式達(dá)到最好的結(jié)果。

      糟糕的情形:忙等待

      執(zhí)行此等待的最簡(jiǎn)單和最直觀的方法是使用while循環(huán):

       # wait here for the result to be available before continuing    while result is None:        pass

      如果要嘗試此操作,可以對(duì)以下完整腳本進(jìn)行復(fù)制/粘貼:

      from random import randomimport threadingimport timeresult = Nonedef background_calculation(): # here goes some long calculation time.sleep(random() * 5 * 60) # when the calculation is done, the result is stored in a global variable global result result = 42def main(): thread = threading.Thread(target=background_calculation) thread.start() # wait here for the result to be available before continuing while result is None: pass print('The result is', result)if __name__ == '__main__': main()

      這是一種很糟糕的等待方式。你能告訴我為什么嗎?

      如果你想體驗(yàn)它,可以在你的系統(tǒng)上嘗試這個(gè)腳本。腳本運(yùn)行后,在Windows上打開(kāi)任務(wù)管理器,在Mac上打開(kāi)活動(dòng)監(jiān)視器。如果你偏愛(ài)命令行,也可以執(zhí)行top,看看CPU的使用情況,并注意它是如何暴增的。

      這個(gè)while循環(huán)看起來(lái)是一個(gè)空循環(huán),實(shí)際上大部分情況下都是如此。唯一的例外是需要反復(fù)檢查循環(huán)退出條件,以確定何時(shí)退出循環(huán)。因此,當(dāng)循環(huán)體完全為空時(shí),Python被迫不斷地計(jì)算result is None。

      實(shí)際上,循環(huán)為空的事實(shí)使Python完全專(zhuān)注于盡可能快地重復(fù)這個(gè)計(jì)算,消耗大量的CPU,并使在該CPU上運(yùn)行的其他所有計(jì)算都慢了很多!

      這種類(lèi)型的等待通常稱為忙等待。在這種情況下,一個(gè)CPU做了很多無(wú)用功,我們稱之為空轉(zhuǎn)。千萬(wàn)別這樣。

      壞方案:在忙等待中使用睡眠函數(shù)

      有趣的是,在上一節(jié)中的忙等待示例中,有人會(huì)認(rèn)為擁有空循環(huán)應(yīng)該會(huì)減少CPU的工作,但事實(shí)上恰恰相反。

      因此,對(duì)前一個(gè)解決方案的明顯改進(jìn)是在while循環(huán)中添加一些東西,這會(huì)給CPU提供一個(gè)制動(dòng)器,從瘋狂地檢查while循環(huán)條件中退出。

      我相信你們中的很多人可能猜到用什么方法實(shí)現(xiàn)了,那就是調(diào)用sleep函數(shù):

       # wait here for the result to be available before continuing    while result is None:        time.sleep(15)

      如果你想在本地運(yùn)行它,下面是完整的腳本:

      from random import randomimport threadingimport timeresult = Nonedef background_calculation(): # here goes some long calculation time.sleep(random() * 5 * 60) # when the calculation is done, the result is stored in a global variable global result result = 42def main(): thread = threading.Thread(target=background_calculation) thread.start() # wait here for the result to be available before continuing while result is None: time.sleep(15) print('The result is', result)if __name__ == '__main__': main()

      函數(shù)time.sleep()的參數(shù)傳入秒數(shù),該時(shí)間內(nèi)程序暫停執(zhí)行。上面的例子中,在每個(gè)循環(huán)中休眠15秒,這意味著Python只會(huì)以每分鐘四次的速率來(lái)評(píng)估循環(huán)的退出條件,而與先前版本中的速度一樣快。

      在這15秒的睡眠中,CPU將不執(zhí)行本程序的任何操作,則可以承擔(dān)其他進(jìn)程中的工作。

      如果你嘗試這個(gè)版本的程序,會(huì)發(fā)現(xiàn)腳本在等待時(shí)沒(méi)有使CPU工作過(guò)度,因此你可能認(rèn)為我們現(xiàn)在有了完美的解決方案。但是,我把這個(gè)版本的程序也稱為壞方案,不是嗎?

      雖然這個(gè)解決方案比前一個(gè)好很多,但有兩個(gè)問(wèn)題仍然使它不太理想。首先,這個(gè)循環(huán)仍然可以稱為忙等待。

      它使用的CPU比前一個(gè)要少得多,但我們?nèi)匀挥幸粋€(gè)正在旋轉(zhuǎn)的CPU。我們只是通過(guò)降低運(yùn)算的頻率使它變得可以忍受。

      在我看來(lái),第二個(gè)問(wèn)題更令人關(guān)注。假設(shè)正在進(jìn)行此計(jì)算的后臺(tái)任務(wù)只需61秒即可完成其工作并生成結(jié)果。

      如果我們的等待循環(huán)與任務(wù)同時(shí)開(kāi)始,它將在0、15、30、45、60和75秒檢查結(jié)果變量的值。第60秒的檢查仍然返回False,因?yàn)楹笈_(tái)任務(wù)還有一秒才能完成,所以第75秒的檢查將導(dǎo)致循環(huán)退出。

      你看到問(wèn)題了嗎?循環(huán)在75秒退出,但是后臺(tái)任務(wù)在61秒完成,所以等待延長(zhǎng)了額外的14秒!

      雖然這種類(lèi)型的等待非常常見(jiàn),但它有一個(gè)沖突問(wèn)題,即等待的時(shí)長(zhǎng)是在循環(huán)中所設(shè)置的睡眠時(shí)長(zhǎng)的倍數(shù)。

      如果睡眠時(shí)間較少,等待時(shí)間會(huì)更精確,但是CPU使用率會(huì)因?yàn)槊Φ却仙?。如果睡眠時(shí)間較多,CPU使用率就更少,但你可能最終等待的時(shí)間比需要的時(shí)間要長(zhǎng)得多。

      優(yōu)秀方案1:連接線程

      假設(shè)我們希望等待盡可能有效,等待在某個(gè)線程產(chǎn)生結(jié)果的確切時(shí)刻結(jié)束,怎么才能做到呢?

      僅使用Python邏輯來(lái)實(shí)現(xiàn)的解決方案(如前兩個(gè))行不通,因?yàn)橐_定線程是否完成,我們需要運(yùn)行一些Python代碼。

      如果我們經(jīng)常運(yùn)行檢查,就會(huì)占用大量的CPU;如果我們不經(jīng)常運(yùn)行檢查,就會(huì)錯(cuò)過(guò)線程完成的確切時(shí)刻。我們已經(jīng)在前兩部分中清楚地看到了這一點(diǎn)。

      為了能夠有效地等待,我們需要操作系統(tǒng)的外部幫助。操作系統(tǒng)可以在發(fā)生某些事件時(shí)有效地通知我們用程序,特別是,它可以告訴我們線程何時(shí)退出。這一操作被稱為連接線程。

      Python標(biāo)準(zhǔn)庫(kù)中的threading.Thread有一個(gè)join()方法,該方法將在線程退出的確切時(shí)刻得到返回值:

      # wait here for the result to be available before continuing    thread.join()

      以下是完整的腳本:

      from random import randomimport threadingimport timeresult = Nonedef background_calculation(): # here goes some long calculation time.sleep(random() * 5 * 60) # when the calculation is done, the result is stored in a global variable global result result = 42def main(): thread = threading.Thread(target=background_calculation) thread.start() # wait here for the result to be available before continuing thread.join() print('The result is', result)if __name__ == '__main__': main()

      調(diào)用join()與time.sleep()一樣都會(huì)產(chǎn)生阻塞,但它不是在固定時(shí)間阻塞,而是在后臺(tái)線程運(yùn)行時(shí)阻塞。在線程完成時(shí),join()函數(shù)執(zhí)行,應(yīng)用程序可以繼續(xù)。操作系統(tǒng)使高效的等待變得容易多了!

      優(yōu)秀方案2:等待事件

      如果你需要等待線程完成,那么我在上一節(jié)中介紹的模式就是你應(yīng)該使用的。但是,當(dāng)然,在許多其他情況下,你可能需要等待線程以外的事情,那么,如何等待某種不綁定到線程的普通事件或其他操作系統(tǒng)資源呢?

      為了向你展示如何做到這一點(diǎn),我將修改我一直使用的示例中的背景線程,使其更加復(fù)雜。

      這個(gè)線程仍然會(huì)產(chǎn)生一個(gè)結(jié)果,但是它不會(huì)馬上退出,它將繼續(xù)運(yùn)行并執(zhí)行更多工作:

      from random import randomimport threadingimport timeresult = Nonedef background_calculation():    # here goes some long calculation    time.sleep(random() * 5 * 60)    # when the calculation is done, the result is stored in a global variable    global result    result = 42    # do some more work before exiting the thread    time.sleep(10)def main():    thread = threading.Thread(target=background_calculation)    thread.start()    # wait here for the result to be available before continuing    thread.join()    print('The result is', result)if __name__ == '__main__':    main()

      如果運(yùn)行上述版本的示例,結(jié)果報(bào)告將延遲10秒。因?yàn)樵谏山Y(jié)果后,線程將保持運(yùn)行那么長(zhǎng)時(shí)間。但是,我們想在有結(jié)果的確切時(shí)刻報(bào)告結(jié)果。

      像這樣,你需要在任意條件下實(shí)現(xiàn)等待,可以使用Event對(duì)象,它來(lái)自Python標(biāo)準(zhǔn)庫(kù)中的threading包。以下是創(chuàng)建事件的過(guò)程:

      result_available = threading.Event()

      Events have a wait() method, which we will use to write our wait:

      Event實(shí)例有一個(gè)wait()方法,我們將使用它來(lái)編寫(xiě)等待:

      # wait here for the result to be available before continuing    result_available.wait()

      Event.wait()和Thread.join()的區(qū)別在于后者預(yù)先指定為等待特定事件,即線程的結(jié)束。前者是一個(gè)通用事件,可以等待任何事件,那么,如果這個(gè)事件對(duì)象可以在任何情況下等待,我們?nèi)绾胃嬖V它何時(shí)結(jié)束等待?

      為此,Event對(duì)象有一個(gè)set()方法,在后臺(tái)線程設(shè)置result全局變量后,它可以立即設(shè)置事件,從而導(dǎo)致等待它的任何代碼解除阻塞:

      # when the calculation is done, the result is stored in a global variable global result result = 42 result_available.set()

      下面是這個(gè)例子的完整代碼:

      from random import randomimport threadingimport timeresult = Noneresult_available = threading.Event()def background_calculation():    # here goes some long calculation    time.sleep(random() * 5 * 60)    # when the calculation is done, the result is stored in a global variable    global result    result = 42    result_available.set()    # do some more work before exiting the thread    time.sleep(10)def main():    thread = threading.Thread(target=background_calculation)    thread.start()    # wait here for the result to be available before continuing    result_available.wait()    print('The result is', result)if __name__ == '__main__':    main()

      所以,在這里你可以看到后臺(tái)線程和主線程是如何圍繞這個(gè)Event對(duì)象同步的。

      優(yōu)秀方案3:顯示進(jìn)度百分比時(shí)等待

      Event對(duì)象的一個(gè)優(yōu)點(diǎn)是它們是通用的,所以如果你運(yùn)用一點(diǎn)創(chuàng)造力,會(huì)發(fā)現(xiàn)很多情況下它們都是有用的。例如,在編寫(xiě)后臺(tái)線程函數(shù)時(shí),請(qǐng)考慮以下常見(jiàn)模式:

      exit_thread = Falsedef background_thread(): while not exit_thread: # do some work time.sleep(10)

      在這里,我們?cè)噲D編寫(xiě)一個(gè)線程,這個(gè)線程可以通過(guò)將全局變量exit_thread設(shè)置為T(mén)rue來(lái)優(yōu)雅地終止。這是一個(gè)非常常見(jiàn)的模式,但現(xiàn)在你可能確定了為什么這不是一個(gè)很好的解決方案,對(duì)吧?

      從exit_thread變量設(shè)置到線程實(shí)際退出時(shí),最多需要10秒。也就是說(shuō),不必計(jì)算線程到達(dá)sleep語(yǔ)句之前可能經(jīng)過(guò)的額外時(shí)間。

      借助Event.wait()方法的timeout參數(shù),我們可以使用Event對(duì)象以更有效的方式編寫(xiě)線程:

      exit_thread = threading.Event()def background_thread():    while True:        # do some work        if exit_thread.wait(timeout=10):            break

      通過(guò)這個(gè)實(shí)現(xiàn),我們已經(jīng)用Event對(duì)象的智能等待替換了固定時(shí)間的睡眠。在每次迭代結(jié)束時(shí),仍睡眠10秒,但是如果事件的set()方法在其他地方被調(diào)用的同時(shí),線程被困在exit_thread.wait(timeout=10)中,那么調(diào)用將立即返回True,線程將退出。

      如果超時(shí)時(shí)間達(dá)到10秒,則wait()調(diào)用返回False,線程繼續(xù)運(yùn)行循環(huán),因此它與調(diào)用time.sleep(10)的結(jié)果相同。

      如果在線程沒(méi)有運(yùn)行循環(huán)時(shí),程序的其他部分調(diào)用exit_thread.set(),那么線程將繼續(xù)運(yùn)行。

      但當(dāng)它到達(dá)exit_thread.wait()調(diào)用時(shí),它將立即返回True并退出。能夠在不必等待太久的情況下終止線程的秘密是:確保經(jīng)常檢查事件對(duì)象。

      讓我用這個(gè)timeout參數(shù)向你展示一個(gè)更完整的示例。我要做的是從上一節(jié)中獲取代碼,并在等待過(guò)程中展開(kāi)它以顯示完成的百分比。

      首先,讓我們將進(jìn)度報(bào)告添加到后臺(tái)線程。在原來(lái)的版本中,睡眠的隨機(jī)數(shù)秒最多達(dá)到300,也就是5分鐘。為了報(bào)告這段時(shí)間內(nèi)的任務(wù)進(jìn)度,我將用一個(gè)循環(huán)替換單個(gè)sleep。

      該循環(huán)運(yùn)行100個(gè)迭代,在每個(gè)迭代中稍微休眠,這將使我有機(jī)會(huì)報(bào)告每個(gè)迭代中的進(jìn)度百分比。因?yàn)榇笏叱掷m(xù)了300秒,現(xiàn)在我要分成100次睡眠,每次最多3秒。

      總的來(lái)說(shuō),這項(xiàng)任務(wù)將花費(fèi)相同的隨機(jī)時(shí)間,但是將工作劃分為100份可以很容易地報(bào)告完成的百分比。

      下面是對(duì)后臺(tái)線程的更改,用于報(bào)告進(jìn)度全局變量中的進(jìn)度百分比:

      progress = 0def background_calculation(): # here goes some long calculation global progress for i in range(100): time.sleep(random() * 3) progress = i + 1 # ...

      現(xiàn)在我們可以構(gòu)建一個(gè)更智能的等待,每5秒報(bào)告完成的百分比:

       # wait here for the result to be available before continuing    while not result_available.wait(timeout=5):        print('\r{}% done...'.format(progress), end='', flush=True)    print('\r{}% done...'.format(progress))

      這個(gè)新while循環(huán)將等待result_available事件長(zhǎng)達(dá)5秒,并以此作為退出條件。如果在這個(gè)時(shí)間間隔內(nèi)沒(méi)有發(fā)生任何事情,那么wait()將返回False。

      我們進(jìn)入循環(huán),在循環(huán)中打印progress變量的當(dāng)前值。請(qǐng)注意,我使用\r字符和print()函數(shù)的end=、flush=True參數(shù)來(lái)防止終端跳轉(zhuǎn)到下一行。

      這個(gè)技巧允許我們將內(nèi)容打印在一行,因此每個(gè)進(jìn)度行將在前一行的上方打印。

      當(dāng)背景計(jì)算的Event對(duì)象調(diào)用set()時(shí),循環(huán)將退出,因?yàn)閣ait()將立即返回True,此時(shí),我再打印一次,這次使用默認(rèn)的行結(jié)束符。這樣我就打印了最后的百分比,并且終端已經(jīng)準(zhǔn)備好打印下一行結(jié)果了。

      如果你想運(yùn)行它或更詳細(xì)地研究它,下面是完整的代碼:

      from random import randomimport threadingimport timeprogress = 0result = Noneresult_available = threading.Event()def background_calculation(): # here goes some long calculation global progress for i in range(100): time.sleep(random() * 3) progress = i + 1 # when the calculation is done, the result is stored in a global variable global result result = 42 result_available.set() # do some more work before exiting the thread time.sleep(10)def main(): thread = threading.Thread(target=background_calculation) thread.start() # wait here for the result to be available before continuing while not result_available.wait(timeout=5): print('\r{}% done...'.format(progress), end='', flush=True) print('\r{}% done...'.format(progress)) print('The result is', result)if __name__ == '__main__': main()

      更多方法!

      Event對(duì)象并不是在應(yīng)用程序中解決等待問(wèn)題的唯一方法,還有更多的方法。其中一些方法可能比它更合適,這取決于你在等待什么。

      如果需要查看文件目錄并在文件被刪除時(shí)或在現(xiàn)有文件被修改時(shí)對(duì)文件進(jìn)行操作,則Event不會(huì)有用,因?yàn)樵O(shè)置事件的條件在應(yīng)用程序外部。

      在這種情況下,你需要使用操作系統(tǒng)提供的工具來(lái)監(jiān)視文件系統(tǒng)事件。在Python中,可以使用watchdog包,它封裝了針對(duì)不同操作系統(tǒng)中的文件監(jiān)視API。

      如果需要等待子進(jìn)程結(jié)束,則子進(jìn)程包提供了一些用于啟動(dòng)和等待進(jìn)程的函數(shù)。

      如果你需要從網(wǎng)絡(luò)socket讀取數(shù)據(jù),socket的默認(rèn)配置將使你的讀取阻塞,直到數(shù)據(jù)到達(dá),因此這是一個(gè)有效的等待。如果需要在多個(gè)socket或其他文件上實(shí)現(xiàn)等待,那么Python標(biāo)準(zhǔn)庫(kù)中的select模塊封裝了操作系統(tǒng)中實(shí)現(xiàn)有效等待的函數(shù)。

      如果要編寫(xiě)生成且/或使用者數(shù)據(jù)的程序,則可以使用Queue對(duì)象?!吧a(chǎn)者”將項(xiàng)目添加到隊(duì)列中,而“消費(fèi)者”則高效地等待從隊(duì)列中獲取項(xiàng)目。

      正如你所看到的,在大多數(shù)情況下,操作系統(tǒng)提供了有效的等待機(jī)制,所以你只需要弄清楚如何在Python中訪問(wèn)這些機(jī)制。

      異步等待

      如果你使用的是asyncio包,則可以訪問(wèn)與這些類(lèi)型相似的等待函數(shù)。例如,有一些asyncio.Event和asyncio.Queue對(duì)象是根據(jù)標(biāo)準(zhǔn)庫(kù)中的原始對(duì)象建模的,但它們是基于異步類(lèi)型的。

      結(jié)論

      建議你使用我提供的所有示例來(lái)熟悉這些技術(shù),并最終使用它們來(lái)替換你的代碼中效率低下的time.sleep()!

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶 評(píng)論公約

        類(lèi)似文章 更多