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

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

    • 分享

      Python學(xué)習(xí)教程_Python學(xué)習(xí)路線:Day13-進(jìn)程和線程

       千鋒Python學(xué)堂 2019-05-24

      Python學(xué)習(xí)教程(Python學(xué)習(xí)路線):進(jìn)程和線程

      今天我們使用的計(jì)算機(jī)早已進(jìn)入多CPU或多核時(shí)代,而我們使用的操作系統(tǒng)都是支持“多任務(wù)”的操作系統(tǒng),這使得我們可以同時(shí)運(yùn)行多個(gè)程序,也可以將一個(gè)程序分解為若干個(gè)相對(duì)獨(dú)立的子任務(wù),讓多個(gè)子任務(wù)并發(fā)的執(zhí)行,從而縮短程序的執(zhí)行時(shí)間,同時(shí)也讓用戶獲得更好的體驗(yàn)。因此在當(dāng)下不管是用什么編程語(yǔ)言進(jìn)行開(kāi)發(fā),實(shí)現(xiàn)讓程序同時(shí)執(zhí)行多個(gè)任務(wù)也就是常說(shuō)的“并發(fā)編程”,應(yīng)該是程序員必備技能之一。為此,我們需要先討論兩個(gè)概念,一個(gè)叫進(jìn)程,一個(gè)叫線程。

      概念

      進(jìn)程就是操作系統(tǒng)中執(zhí)行的一個(gè)程序,操作系統(tǒng)以進(jìn)程為單位分配存儲(chǔ)空間,每個(gè)進(jìn)程都有自己的地址空間、數(shù)據(jù)棧以及其他用于跟蹤進(jìn)程執(zhí)行的輔助數(shù)據(jù),操作系統(tǒng)管理所有進(jìn)程的執(zhí)行,為它們合理的分配資源。進(jìn)程可以通過(guò)fork或spawn的方式來(lái)創(chuàng)建新的進(jìn)程來(lái)執(zhí)行其他的任務(wù),不過(guò)新的進(jìn)程也有自己獨(dú)立的內(nèi)存空間,因此必須通過(guò)進(jìn)程間通信機(jī)制(IPC,Inter-Process Communication)來(lái)實(shí)現(xiàn)數(shù)據(jù)共享,具體的方式包括管道、信號(hào)、套接字、共享內(nèi)存區(qū)等。

      一個(gè)進(jìn)程還可以擁有多個(gè)并發(fā)的執(zhí)行線索,簡(jiǎn)單的說(shuō)就是擁有多個(gè)可以獲得CPU調(diào)度的執(zhí)行單元,這就是所謂的線程。由于線程在同一個(gè)進(jìn)程下,它們可以共享相同的上下文,因此相對(duì)于進(jìn)程而言,線程間的信息共享和通信更加容易。當(dāng)然在單核CPU系統(tǒng)中,真正的并發(fā)是不可能的,因?yàn)樵谀硞€(gè)時(shí)刻能夠獲得CPU的只有唯一的一個(gè)線程,多個(gè)線程共享了CPU的執(zhí)行時(shí)間。使用多線程實(shí)現(xiàn)并發(fā)編程為程序帶來(lái)的好處是不言而喻的,最主要的體現(xiàn)在提升程序的性能和改善用戶體驗(yàn),今天我們使用的軟件幾乎都用到了多線程技術(shù),這一點(diǎn)可以利用系統(tǒng)自帶的進(jìn)程監(jiān)控工具(如macOS中的“活動(dòng)監(jiān)視器”、Windows中的“任務(wù)管理器”)來(lái)證實(shí),如下圖所示。

      當(dāng)然多線程也并不是沒(méi)有壞處,站在其他進(jìn)程的角度,多線程的程序?qū)ζ渌绦虿⒉挥押?,因?yàn)樗加昧烁嗟腃PU執(zhí)行時(shí)間,導(dǎo)致其他程序無(wú)法獲得足夠的CPU執(zhí)行時(shí)間;另一方面,站在開(kāi)發(fā)者的角度,編寫(xiě)和調(diào)試多線程的程序都對(duì)開(kāi)發(fā)者有較高的要求,對(duì)于初學(xué)者來(lái)說(shuō)更加困難。

      Python既支持多進(jìn)程又支持多線程,因此使用Python實(shí)現(xiàn)并發(fā)編程主要有3種方式:多進(jìn)程、多線程、多進(jìn)程+多線程。

      Python中的多進(jìn)程

      Unix和Linux操作系統(tǒng)上提供了fork()系統(tǒng)調(diào)用來(lái)創(chuàng)建進(jìn)程,調(diào)用fork()函數(shù)的是父進(jìn)程,創(chuàng)建出的是子進(jìn)程,子進(jìn)程是父進(jìn)程的一個(gè)拷貝,但是子進(jìn)程擁有自己的PID。fork()函數(shù)非常特殊它會(huì)返回兩次,父進(jìn)程中可以通過(guò)fork()函數(shù)的返回值得到子進(jìn)程的PID,而子進(jìn)程中的返回值永遠(yuǎn)都是0。Python的os模塊提供了fork()函數(shù)。由于Windows系統(tǒng)沒(méi)有fork()調(diào)用,因此要實(shí)現(xiàn)跨平臺(tái)的多進(jìn)程編程,可以使用multiprocessing模塊的Process類來(lái)創(chuàng)建子進(jìn)程,而且該模塊還提供了更高級(jí)的封裝,例如批量啟動(dòng)進(jìn)程的進(jìn)程池(Pool)、用于進(jìn)程間通信的隊(duì)列(Queue)和管道(Pipe)等。

      下面用一個(gè)下載文件的例子來(lái)說(shuō)明使用多進(jìn)程和不使用多進(jìn)程到底有什么差別,先看看下面的代碼。

      from random import randintfrom time import time, sleepdef download_task(filename):    print('開(kāi)始下載%s...' % filename)
          time_to_download = randint(5, 10)
          sleep(time_to_download)    print('%s下載完成! 耗費(fèi)了%d' % (filename, time_to_download))def main():
          start = time()
          download_task('Python從入門(mén)到住院.pdf')
          download_task('Peking Hot.avi')
          end = time()    print('總共耗費(fèi)了%.2f秒.' % (end - start))if __name__ == '__main__':
          main()

      下面是運(yùn)行程序得到的一次運(yùn)行結(jié)果。

      開(kāi)始下載Python從入門(mén)到住院.pdf...
      Python從入門(mén)到住院.pdf下載完成! 耗費(fèi)了6秒
      開(kāi)始下載Peking Hot.avi...
      Peking Hot.avi下載完成! 耗費(fèi)了7秒
      總共耗費(fèi)了13.01秒.

      從上面的例子可以看出,如果程序中的代碼只能按順序一點(diǎn)點(diǎn)的往下執(zhí)行,那么即使執(zhí)行兩個(gè)毫不相關(guān)的下載任務(wù),也需要先等待一個(gè)文件下載完成后才能開(kāi)始下一個(gè)下載任務(wù),很顯然這并不合理也沒(méi)有效率。接下來(lái)我們使用多進(jìn)程的方式將兩個(gè)下載任務(wù)放到不同的進(jìn)程中,代碼如下所示。

      from multiprocessing import Processfrom os import getpidfrom random import randintfrom time import time, sleepdef download_task(filename):    print('啟動(dòng)下載進(jìn)程,進(jìn)程號(hào)[%d].' % getpid())    print('開(kāi)始下載%s...' % filename)
          time_to_download = randint(5, 10)
          sleep(time_to_download)    print('%s下載完成! 耗費(fèi)了%d' % (filename, time_to_download))def main():
          start = time()
          p1 = Process(target=download_task, args=('Python從入門(mén)到住院.pdf', ))
          p1.start()
          p2 = Process(target=download_task, args=('Peking Hot.avi', ))
          p2.start()
          p1.join()
          p2.join()
          end = time()    print('總共耗費(fèi)了%.2f秒.' % (end - start))if __name__ == '__main__':
          main()

      在上面的代碼中,我們通過(guò)Process類創(chuàng)建了進(jìn)程對(duì)象,通過(guò)target參數(shù)我們傳入一個(gè)函數(shù)來(lái)表示進(jìn)程啟動(dòng)后要執(zhí)行的代碼,后面的args是一個(gè)元組,它代表了傳遞給函數(shù)的參數(shù)。Process對(duì)象的start方法用來(lái)啟動(dòng)進(jìn)程,而join方法表示等待進(jìn)程執(zhí)行結(jié)束。運(yùn)行上面的代碼可以明顯發(fā)現(xiàn)兩個(gè)下載任務(wù)“同時(shí)”啟動(dòng)了,而且程序的執(zhí)行時(shí)間將大大縮短,不再是兩個(gè)任務(wù)的時(shí)間總和。下面是程序的一次執(zhí)行結(jié)果。

      啟動(dòng)下載進(jìn)程,進(jìn)程號(hào)[1530].
      開(kāi)始下載Python從入門(mén)到住院.pdf...
      啟動(dòng)下載進(jìn)程,進(jìn)程號(hào)[1531].
      開(kāi)始下載Peking Hot.avi...
      Peking Hot.avi下載完成! 耗費(fèi)了7秒
      Python從入門(mén)到住院.pdf下載完成! 耗費(fèi)了10秒
      總共耗費(fèi)了10.01秒.

      我們也可以使用subprocess模塊中的類和函數(shù)來(lái)創(chuàng)建和啟動(dòng)子進(jìn)程,然后通過(guò)管道來(lái)和子進(jìn)程通信,這些內(nèi)容我們不在此進(jìn)行講解,有興趣的讀者可以自己了解這些知識(shí)。接下來(lái)我們將重點(diǎn)放在如何實(shí)現(xiàn)兩個(gè)進(jìn)程間的通信。我們啟動(dòng)兩個(gè)進(jìn)程,一個(gè)輸出Ping,一個(gè)輸出Pong,兩個(gè)進(jìn)程輸出的Ping和Pong加起來(lái)一共10個(gè)。聽(tīng)起來(lái)很簡(jiǎn)單吧,但是如果這樣寫(xiě)可是錯(cuò)的哦。

      from multiprocessing import Processfrom time import sleep
      
      counter = 0def sub_task(string):    global counter    while counter < 10:        print(string, end='', flush=True)
              counter += 1sleep(0.01)        
      def main():
          Process(target=sub_task, args=('Ping', )).start()
          Process(target=sub_task, args=('Pong', )).start()if __name__ == '__main__':
          main()

      看起來(lái)沒(méi)毛病,但是最后的結(jié)果是Ping和Pong各輸出了10個(gè),Why?當(dāng)我們?cè)诔绦蛑袆?chuàng)建進(jìn)程的時(shí)候,子進(jìn)程復(fù)制了父進(jìn)程及其所有的數(shù)據(jù)結(jié)構(gòu),每個(gè)子進(jìn)程有自己獨(dú)立的內(nèi)存空間,這也就意味著兩個(gè)子進(jìn)程中各有一個(gè)counter變量,所以結(jié)果也就可想而知了。要解決這個(gè)問(wèn)題比較簡(jiǎn)單的辦法是使用multiprocessing模塊中的Queue類,它是可以被多個(gè)進(jìn)程共享的隊(duì)列,底層是通過(guò)管道和信號(hào)量(semaphore)機(jī)制來(lái)實(shí)現(xiàn)的,有興趣的讀者可以自己嘗試一下。

      Python中的多線程

      在Python早期的版本中就引入了thread模塊(現(xiàn)在名為_(kāi)thread)來(lái)實(shí)現(xiàn)多線程編程,然而該模塊過(guò)于底層,而且很多功能都沒(méi)有提供,因此目前的多線程開(kāi)發(fā)我們推薦使用threading模塊,該模塊對(duì)多線程編程提供了更好的面向?qū)ο蟮姆庋b。我們把剛才下載文件的例子用多線程的方式來(lái)實(shí)現(xiàn)一遍。

      from random import randintfrom threading import Threadfrom time import time, sleepdef download(filename):    print('開(kāi)始下載%s...' % filename)
          time_to_download = randint(5, 10)
          sleep(time_to_download)    print('%s下載完成! 耗費(fèi)了%d' % (filename, time_to_download))def main():
          start = time()
          t1 = Thread(target=download, args=('Python從入門(mén)到住院.pdf',))
          t1.start()
          t2 = Thread(target=download, args=('Peking Hot.avi',))
          t2.start()
          t1.join()
          t2.join()
          end = time()    print('總共耗費(fèi)了%.3f' % (end - start))if __name__ == '__main__':
          main()

      我們可以直接使用threading模塊的Thread類來(lái)創(chuàng)建線程,但是我們之前講過(guò)一個(gè)非常重要的概念叫“繼承”,我們可以從已有的類創(chuàng)建新類,因此也可以通過(guò)繼承Thread類的方式來(lái)創(chuàng)建自定義的線程類,然后再創(chuàng)建線程對(duì)象并啟動(dòng)線程。代碼如下所示。

      from random import randintfrom threading import Threadfrom time import time, sleepclass DownloadTask(Thread):    def __init__(self, filename):        super().__init__()        self._filename = filename    def run(self):        print('開(kāi)始下載%s...' % self._filename)
              time_to_download = randint(5, 10)
              sleep(time_to_download)        print('%s下載完成! 耗費(fèi)了%d' % (self._filename, time_to_download))def main():
          start = time()
          t1 = DownloadTask('Python從入門(mén)到住院.pdf')
          t1.start()
          t2 = DownloadTask('Peking Hot.avi')
          t2.start()
          t1.join()
          t2.join()
          end = time()    print('總共耗費(fèi)了%.2f秒.' % (end - start))if __name__ == '__main__':
          main()

      因?yàn)槎鄠€(gè)線程可以共享進(jìn)程的內(nèi)存空間,因此要實(shí)現(xiàn)多個(gè)線程間的通信相對(duì)簡(jiǎn)單,大家能想到的最直接的辦法就是設(shè)置一個(gè)全局變量,多個(gè)線程共享這個(gè)全局變量即可。但是當(dāng)多個(gè)線程共享同一個(gè)變量(我們通常稱之為“資源”)的時(shí)候,很有可能產(chǎn)生不可控的結(jié)果從而導(dǎo)致程序失效甚至崩潰。如果一個(gè)資源被多個(gè)線程競(jìng)爭(zhēng)使用,那么我們通常稱之為“臨界資源”,對(duì)“臨界資源”的訪問(wèn)需要加上保護(hù),否則資源會(huì)處于“混亂”的狀態(tài)。下面的例子演示了100個(gè)線程向同一個(gè)銀行賬戶轉(zhuǎn)賬(轉(zhuǎn)入1元錢(qián))的場(chǎng)景,在這個(gè)例子中,銀行賬戶就是一個(gè)臨界資源,在沒(méi)有保護(hù)的情況下我們很有可能會(huì)得到錯(cuò)誤的結(jié)果。

      from time import sleepfrom threading import Threadclass Account(object):    def __init__(self):        self._balance = 0def deposit(self, money):        # 計(jì)算存款后的余額new_balance = self._balance + money        # 模擬受理存款業(yè)務(wù)需要0.01秒的時(shí)間sleep(0.01)        # 修改賬戶余額self._balance = new_balance    @propertydef balance(self):        return self._balanceclass AddMoneyThread(Thread):    def __init__(self, account, money):        super().__init__()        self._account = account        self._money = money    def run(self):        self._account.deposit(self._money)def main():
          account = Account()
          threads = []    # 創(chuàng)建100個(gè)存款的線程向同一個(gè)賬戶中存錢(qián)for _ in range(100):
              t = AddMoneyThread(account, 1)
              threads.append(t)
              t.start()    # 等所有存款的線程都執(zhí)行完畢for t in threads:
              t.join()    print('賬戶余額為: ¥%d' % account.balance)if __name__ == '__main__':
          main()

      運(yùn)行上面的程序,結(jié)果讓人大跌眼鏡,100個(gè)線程分別向賬戶中轉(zhuǎn)入1元錢(qián),結(jié)果居然遠(yuǎn)遠(yuǎn)小于100元。之所以出現(xiàn)這種情況是因?yàn)槲覀儧](méi)有對(duì)銀行賬戶這個(gè)“臨界資源”加以保護(hù),多個(gè)線程同時(shí)向賬戶中存錢(qián)時(shí),會(huì)一起執(zhí)行到new_balance = self._balance + money這行代碼,多個(gè)線程得到的賬戶余額都是初始狀態(tài)下的0,所以都是0上面做了+1的操作,因此得到了錯(cuò)誤的結(jié)果。在這種情況下,“鎖”就可以派上用場(chǎng)了。我們可以通過(guò)“鎖”來(lái)保護(hù)“臨界資源”,只有獲得“鎖”的線程才能訪問(wèn)“臨界資源”,而其他沒(méi)有得到“鎖”的線程只能被阻塞起來(lái),直到獲得“鎖”的線程釋放了“鎖”,其他線程才有機(jī)會(huì)獲得“鎖”,進(jìn)而訪問(wèn)被保護(hù)的“臨界資源”。下面的代碼演示了如何使用“鎖”來(lái)保護(hù)對(duì)銀行賬戶的操作,從而獲得正確的結(jié)果。

      from time import sleepfrom threading import Thread, Lockclass Account(object):    def __init__(self):        self._balance = 0self._lock = Lock()    def deposit(self, money):        # 先獲取鎖才能執(zhí)行后續(xù)的代碼self._lock.acquire()        try:
                  new_balance = self._balance + money
                  sleep(0.01)            self._balance = new_balance        finally:            # 在finally中執(zhí)行釋放鎖的操作保證正常異常鎖都能釋放self._lock.release()    @propertydef balance(self):        return self._balanceclass AddMoneyThread(Thread):    def __init__(self, account, money):        super().__init__()        self._account = account        self._money = money    def run(self):        self._account.deposit(self._money)def main():
          account = Account()
          threads = []    for _ in range(100):
              t = AddMoneyThread(account, 1)
              threads.append(t)
              t.start()    for t in threads:
              t.join()    print('賬戶余額為: ¥%d' % account.balance)if __name__ == '__main__':
          main()

      比較遺憾的一件事情是Python的多線程并不能發(fā)揮CPU的多核特性,這一點(diǎn)只要啟動(dòng)幾個(gè)執(zhí)行死循環(huán)的線程就可以得到證實(shí)了。之所以如此,是因?yàn)镻ython的解釋器有一個(gè)“全局解釋器鎖”(GIL)的東西,任何線程執(zhí)行前必須先獲得GIL鎖,然后每執(zhí)行100條字節(jié)碼,解釋器就自動(dòng)釋放GIL鎖,讓別的線程有機(jī)會(huì)執(zhí)行,這是一個(gè)歷史遺留問(wèn)題,但是即便如此,就如我們之前舉的例子,使用多線程在提升執(zhí)行效率和改善用戶體驗(yàn)方面仍然是有積極意義的。

      多進(jìn)程還是多線程

      無(wú)論是多進(jìn)程還是多線程,只要數(shù)量一多,效率肯定上不去,為什么呢?我們打個(gè)比方,假設(shè)你不幸正在準(zhǔn)備中考,每天晚上需要做語(yǔ)文、數(shù)學(xué)、英語(yǔ)、物理、化學(xué)這5科的作業(yè),每項(xiàng)作業(yè)耗時(shí)1小時(shí)。如果你先花1小時(shí)做語(yǔ)文作業(yè),做完了,再花1小時(shí)做數(shù)學(xué)作業(yè),這樣,依次全部做完,一共花5小時(shí),這種方式稱為單任務(wù)模型。如果你打算切換到多任務(wù)模型,可以先做1分鐘語(yǔ)文,再切換到數(shù)學(xué)作業(yè),做1分鐘,再切換到英語(yǔ),以此類推,只要切換速度足夠快,這種方式就和單核CPU執(zhí)行多任務(wù)是一樣的了,以旁觀者的角度來(lái)看,你就正在同時(shí)寫(xiě)5科作業(yè)。

      但是,切換作業(yè)是有代價(jià)的,比如從語(yǔ)文切到數(shù)學(xué),要先收拾桌子上的語(yǔ)文書(shū)本、鋼筆(這叫保存現(xiàn)場(chǎng)),然后,打開(kāi)數(shù)學(xué)課本、找出圓規(guī)直尺(這叫準(zhǔn)備新環(huán)境),才能開(kāi)始做數(shù)學(xué)作業(yè)。操作系統(tǒng)在切換進(jìn)程或者線程時(shí)也是一樣的,它需要先保存當(dāng)前執(zhí)行的現(xiàn)場(chǎng)環(huán)境(CPU寄存器狀態(tài)、內(nèi)存頁(yè)等),然后,把新任務(wù)的執(zhí)行環(huán)境準(zhǔn)備好(恢復(fù)上次的寄存器狀態(tài),切換內(nèi)存頁(yè)等),才能開(kāi)始執(zhí)行。這個(gè)切換過(guò)程雖然很快,但是也需要耗費(fèi)時(shí)間。如果有幾千個(gè)任務(wù)同時(shí)進(jìn)行,操作系統(tǒng)可能就主要忙著切換任務(wù),根本沒(méi)有多少時(shí)間去執(zhí)行任務(wù)了,這種情況最常見(jiàn)的就是硬盤(pán)狂響,點(diǎn)窗口無(wú)反應(yīng),系統(tǒng)處于假死狀態(tài)。所以,多任務(wù)一旦多到一個(gè)限度,反而會(huì)使得系統(tǒng)性能急劇下降,最終導(dǎo)致所有任務(wù)都做不好。

      是否采用多任務(wù)的第二個(gè)考慮是任務(wù)的類型,可以把任務(wù)分為計(jì)算密集型和I/O密集型。計(jì)算密集型任務(wù)的特點(diǎn)是要進(jìn)行大量的計(jì)算,消耗CPU資源,比如對(duì)視頻進(jìn)行編碼解碼或者格式轉(zhuǎn)換等等,這種任務(wù)全靠CPU的運(yùn)算能力,雖然也可以用多任務(wù)完成,但是任務(wù)越多,花在任務(wù)切換的時(shí)間就越多,CPU執(zhí)行任務(wù)的效率就越低。計(jì)算密集型任務(wù)由于主要消耗CPU資源,這類任務(wù)用Python這樣的腳本語(yǔ)言去執(zhí)行效率通常很低,最能勝任這類任務(wù)的是C語(yǔ)言,我們之前提到了Python中有嵌入C/C++代碼的機(jī)制。

      除了計(jì)算密集型任務(wù),其他的涉及到網(wǎng)絡(luò)、存儲(chǔ)介質(zhì)I/O的任務(wù)都可以視為I/O密集型任務(wù),這類任務(wù)的特點(diǎn)是CPU消耗很少,任務(wù)的大部分時(shí)間都在等待I/O操作完成(因?yàn)镮/O的速度遠(yuǎn)遠(yuǎn)低于CPU和內(nèi)存的速度)。對(duì)于I/O密集型任務(wù),如果啟動(dòng)多任務(wù),就可以減少I(mǎi)/O等待時(shí)間從而讓CPU高效率的運(yùn)轉(zhuǎn)。有一大類的任務(wù)都屬于I/O密集型任務(wù),這其中包括了我們很快會(huì)涉及到的網(wǎng)絡(luò)應(yīng)用和Web應(yīng)用。

      說(shuō)明: 上面的內(nèi)容和例子來(lái)自于廖雪峰官方網(wǎng)站的《Python教程》,因?yàn)閷?duì)作者文中的某些觀點(diǎn)持有不同的看法,對(duì)原文的文字描述做了適當(dāng)?shù)恼{(diào)整。

      單線程+異步I/O

      現(xiàn)代操作系統(tǒng)對(duì)I/O操作的改進(jìn)中最為重要的就是支持異步I/O。如果充分利用操作系統(tǒng)提供的異步I/O支持,就可以用單進(jìn)程單線程模型來(lái)執(zhí)行多任務(wù),這種全新的模型稱為事件驅(qū)動(dòng)模型。Nginx就是支持異步I/O的Web服務(wù)器,它在單核CPU上采用單進(jìn)程模型就可以高效地支持多任務(wù)。在多核CPU上,可以運(yùn)行多個(gè)進(jìn)程(數(shù)量與CPU核心數(shù)相同),充分利用多核CPU。用Node.js開(kāi)發(fā)的服務(wù)器端程序也使用了這種工作模式,這也是當(dāng)下實(shí)現(xiàn)多任務(wù)編程的一種趨勢(shì)。

      在Python語(yǔ)言中,單線程+異步I/O的編程模型稱為協(xié)程,有了協(xié)程的支持,就可以基于事件驅(qū)動(dòng)編寫(xiě)高效的多任務(wù)程序。協(xié)程最大的優(yōu)勢(shì)就是極高的執(zhí)行效率,因?yàn)樽映绦蚯袚Q不是線程切換,而是由程序自身控制,因此,沒(méi)有線程切換的開(kāi)銷。協(xié)程的第二個(gè)優(yōu)勢(shì)就是不需要多線程的鎖機(jī)制,因?yàn)橹挥幸粋€(gè)線程,也不存在同時(shí)寫(xiě)變量沖突,在協(xié)程中控制共享資源不用加鎖,只需要判斷狀態(tài)就好了,所以執(zhí)行效率比多線程高很多。如果想要充分利用CPU的多核特性,最簡(jiǎn)單的方法是多進(jìn)程+協(xié)程,既充分利用多核,又充分發(fā)揮協(xié)程的高效率,可獲得極高的性能。關(guān)于這方面的內(nèi)容,我稍后會(huì)做一個(gè)專題來(lái)進(jìn)行講解。

      應(yīng)用案例

      例子1:將耗時(shí)間的任務(wù)放到線程中以獲得更好的用戶體驗(yàn)。

      如下所示的界面中,有“下載”和“關(guān)于”兩個(gè)按鈕,用休眠的方式模擬點(diǎn)擊“下載”按鈕會(huì)聯(lián)網(wǎng)下載文件需要耗費(fèi)10秒的時(shí)間,如果不使用“多線程”,我們會(huì)發(fā)現(xiàn),當(dāng)點(diǎn)擊“下載”按鈕后整個(gè)程序的其他部分都被這個(gè)耗時(shí)間的任務(wù)阻塞而無(wú)法執(zhí)行了,這顯然是非常糟糕的用戶體驗(yàn),代碼如下所示。

      import timeimport tkinterimport tkinter.messageboxdef download():    # 模擬下載任務(wù)需要花費(fèi)10秒鐘時(shí)間time.sleep(10)
          tkinter.messagebox.showinfo('提示', '下載完成!')def show_about():
          tkinter.messagebox.showinfo('關(guān)于', '作者: 駱昊(v1.0)')def main():
          top = tkinter.Tk()
          top.title('單線程')
          top.geometry('200x150')
          top.wm_attributes('-topmost', True)
      
          panel = tkinter.Frame(top)
          button1 = tkinter.Button(panel, text='下載', command=download)
          button1.pack(side='left')
          button2 = tkinter.Button(panel, text='關(guān)于', command=show_about)
          button2.pack(side='right')
          panel.pack(side='bottom')
      
          tkinter.mainloop()if __name__ == '__main__':
          main()

      如果使用多線程將耗時(shí)間的任務(wù)放到一個(gè)獨(dú)立的線程中執(zhí)行,這樣就不會(huì)因?yàn)閳?zhí)行耗時(shí)間的任務(wù)而阻塞了主線程,修改后的代碼如下所示。

      import timeimport tkinterimport tkinter.messageboxfrom threading import Threaddef main():    class DownloadTaskHandler(Thread):        def run(self):
                  time.sleep(10)
                  tkinter.messagebox.showinfo('提示', '下載完成!')            # 啟用下載按鈕button1.config(state=tkinter.NORMAL)    def download():        # 禁用下載按鈕button1.config(state=tkinter.DISABLED)        # 通過(guò)daemon參數(shù)將線程設(shè)置為守護(hù)線程(主程序退出就不再保留執(zhí)行)# 在線程中處理耗時(shí)間的下載任務(wù)DownloadTaskHandler(daemon=True).start()    def show_about():
              tkinter.messagebox.showinfo('關(guān)于', '作者: 駱昊(v1.0)')
      
          top = tkinter.Tk()
          top.title('單線程')
          top.geometry('200x150')
          top.wm_attributes('-topmost', 1)
      
          panel = tkinter.Frame(top)
          button1 = tkinter.Button(panel, text='下載', command=download)
          button1.pack(side='left')
          button2 = tkinter.Button(panel, text='關(guān)于', command=show_about)
          button2.pack(side='right')
          panel.pack(side='bottom')
      
          tkinter.mainloop()if __name__ == '__main__':
          main()

      例子2:使用多進(jìn)程對(duì)復(fù)雜任務(wù)進(jìn)行“分而治之”。

      我們來(lái)完成1~100000000求和的計(jì)算密集型任務(wù),這個(gè)問(wèn)題本身非常簡(jiǎn)單,有點(diǎn)循環(huán)的知識(shí)就能解決,代碼如下所示。

      from time import timedef main():
          total = 0number_list = [x for x in range(1, 100000001)]
          start = time()    for number in number_list:
              total += number    print(total)
          end = time()    print('Execution time: %.3fs' % (end - start))if __name__ == '__main__':
          main()

      在上面的代碼中,我故意先去創(chuàng)建了一個(gè)列表容器然后填入了100000000個(gè)數(shù),這一步其實(shí)是比較耗時(shí)間的,所以為了公平起見(jiàn),當(dāng)我們將這個(gè)任務(wù)分解到8個(gè)進(jìn)程中去執(zhí)行的時(shí)候,我們暫時(shí)也不考慮列表切片操作花費(fèi)的時(shí)間,只是把做運(yùn)算和合并運(yùn)算結(jié)果的時(shí)間統(tǒng)計(jì)出來(lái),代碼如下所示。

      from multiprocessing import Process, Queuefrom random import randintfrom time import timedef task_handler(curr_list, result_queue):
          total = 0for number in curr_list:
              total += number
          result_queue.put(total)def main():
          processes = []
          number_list = [x for x in range(1, 100000001)]
          result_queue = Queue()
          index = 0# 啟動(dòng)8個(gè)進(jìn)程將數(shù)據(jù)切片后進(jìn)行運(yùn)算for _ in range(8):
              p = Process(target=task_handler,                    args=(number_list[index:index + 12500000], result_queue))
              index += 12500000processes.append(p)
              p.start()    # 開(kāi)始記錄所有進(jìn)程執(zhí)行完成花費(fèi)的時(shí)間start = time()    for p in processes:
              p.join()    # 合并執(zhí)行結(jié)果total = 0while not result_queue.empty():
              total += result_queue.get()    print(total)
          end = time()    print('Execution time: ', (end - start), 's', sep='')if __name__ == '__main__':
          main()

      比較兩段代碼的執(zhí)行結(jié)果(在我目前使用的MacBook上,上面的代碼需要大概6秒左右的時(shí)間,而下面的代碼只需要不到1秒的時(shí)間,再?gòu)?qiáng)調(diào)一次我們只是比較了運(yùn)算的時(shí)間,不考慮列表創(chuàng)建及切片操作花費(fèi)的時(shí)間),使用多進(jìn)程后由于獲得了更多的CPU執(zhí)行時(shí)間以及更好的利用了CPU的多核特性,明顯的減少了程序的執(zhí)行時(shí)間,而且計(jì)算量越大效果越明顯。當(dāng)然,如果愿意還可以將多個(gè)進(jìn)程部署在不同的計(jì)算機(jī)上,做成分布式進(jìn)程,具體的做法就是通過(guò)multiprocessing.managers模塊中提供的管理器將Queue對(duì)象通過(guò)網(wǎng)絡(luò)共享出來(lái)(注冊(cè)到網(wǎng)絡(luò)上讓其他計(jì)算機(jī)可以訪問(wèn)),這部分內(nèi)容也留到爬蟲(chóng)的專題再進(jìn)行講解。

        本站是提供個(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)論公約

        類似文章 更多