python線程的使用模式

來源:互聯網
上載者:User

標籤:

為瞭解決阻塞(如I/O)問題,我們需要對程式進行並發設計。

本文將通過將線程和隊列 結合在一起,輕鬆地在 Python 中完成線程編程,建立一些簡單但有效線程使用模式。

 

一、使用線程

先看一個線程不多的例子,不存在阻塞,很簡單:

import threadingimport datetimeclass MyThread(threading.Thread):    def run(self):        now = datetime.datetime.now()        print("{} says Hello World at time: {}".format(self.getName(), now))        for i in range(2):    t = MyThread()    t.start()

    代碼解讀

    1. 兩個線程都輸出了 Hello World 語句,並都帶有日期戳。

    2. 兩個匯入語句:一個匯入了日期時間模組,另一個匯入線程模組。

    3. 類 MyThread 繼承自 threading.Thread,也正因為如此,您需要定義一個 run 方法,以此執行您在該線程中要啟動並執行代碼。

    4. run 方法中的self.getName() 是一個用於確定該線程名稱的方法。

    5. 最後三行代碼實際地調用該類,並啟動線程。如果注意的話,那麼會發現實際啟動線程的是 t.start()

 

 

二、使用線程隊列

如前所述,當多個線程需要共用資料或者資源的時候,可能會使得線程的使用變得複雜。線程模組提供了許多同步原語,包括訊號量、條件變數、事件和鎖。當這些 選項存在時,最佳實務是轉而關注於使用隊列。相比較而言,隊列更容易處理,並且可以使得線程編程更加安全,因為它們能夠有效地傳送單個線程對資源的所有訪 問,並支援更加清晰的、可讀性更強的設計模式。

在下一個樣本中,我們的目的是:擷取網站的 URL,並顯示頁面的前 300 個位元組。

先看看串列方式或者依次執行實現的代碼:

from urllib import requestimport timehosts = ["http://yahoo.com", "http://amazon.com", "http://ibm.com", "http://apple.com"]start = time.time()#grabs urls of hosts and prints first 1024 bytes of pagefor host in hosts:    url = request.urlopen(host)    print(url.read(200))print("Elapsed Time: %s" % (time.time() - start))

    代碼解讀

    1. urllib 模組減少了擷取 Web 頁面的複雜程度。兩次 time.time() 用於計算程式已耗用時間。

    2. 這個程式的執行速度是 13.7 秒,這個結果並不算太好,也不算太糟。

    3. 但如果需要檢索數百個 Web 頁面,那麼按照這個平均值,總時間需要花費大約 1000 秒的時間。如果需要檢索更多頁面呢?

 

 

 下面給出線程化版本:

import queueimport threadingfrom urllib import requestimport timehosts = ["http://yahoo.com", "http://amazon.com", "http://ibm.com", "http://apple.com"]in_queue = queue.Queue()class ThreadUrl(threading.Thread):    """Threaded Url Grab"""    def __init__(self, in_queue):        threading.Thread.__init__(self)        self.in_queue = in_queue    def run(self):        while True:            #grabs host from queue            host = self.in_queue.get()                      #grabs urls of hosts and prints first 1024 bytes of page            url = request.urlopen(host)            print(url.read(200))                      #signals to queue job is done            self.in_queue.task_done()start = time.time()def main():    #spawn a pool of threads, and pass them queue instance     for i in range(4):        t = ThreadUrl(in_queue)        t.setDaemon(True)        t.start()        #populate queue with data           for host in hosts:            in_queue.put(host)         #wait on the queue until everything has been processed         in_queue.join()main()print("Elapsed Time: %s" % (time.time() - start))

    代碼解讀

    1. 與第一個線程樣本相比,它並沒有複雜多少,因為使用了隊列模組。

    2. 建立一個 queue.Queue() 的執行個體,然後使用資料對它進行填充。

    3. 將經過填充資料的執行個體傳遞給線程類,後者是通過繼承 threading.Thread 的方式建立的。

    4. 產生守護線程池。

    5. 每次從隊列中取出一個項目,並使用該線程中的資料和 run 方法以執行相應的工作。

    6. 在完成這項工作之後,使用 queue.task_done() 函數向任務已經完成的隊列發送一個訊號。

    7. 對隊列執行 join 操作,實際上意味著等到隊列為空白,再退出主程式。

在使用這個模式時需要注意一點:通過將守護線程設定為 true,將允許主線程或者程式僅在守護線程處於活動狀態時才能夠退出。這種方式建立了一種簡單的方式以控製程序流程,因為在退出之前,您可以對隊列執行 join 操作、或者等到隊列為空白。

join()保持阻塞狀態,直到處理了隊列中的所有項目為止。在將一個項目添加到該隊列時,未完成的任務的總數就會增加。當使用者線程調用 task_done() 以表示檢索了該項目、並完成了所有的工作時,那麼未完成的任務的總數就會減少。當未完成的任務的總數減少到零時,join() 就會結束阻塞狀態。

 

 

 

三、使用多個隊列

 下一個樣本有兩個隊列。其中一個隊列的各線程擷取的完整 Web 頁面,然後將結果放置到第二個隊列中。然後,對加入到第二個隊列中的另一個線程池進行設定,然後對 Web 頁面執行相應的處理。

提取所訪問的每個頁面的 title 標記,並將其列印輸出。

import queueimport threadingfrom urllib import requestimport timefrom bs4 import BeautifulSouphosts = ["http://yahoo.com", "http://amazon.com", "http://ibm.com", "http://apple.com"]in_queue = queue.Queue()out_queue = queue.Queue()class ThreadUrl(threading.Thread):    """Threaded Url Grab"""    def __init__(self, in_queue, out_queue):        threading.Thread.__init__(self)        self.in_queue = in_queue        self.out_queue = out_queue    def run(self):        while True:            #grabs host from queue            host = self.in_queue.get()            #grabs urls of hosts and then grabs chunk of webpage            url = request.urlopen(host)            chunk = url.read()            #place chunk into out queue            self.out_queue.put(chunk)            #signals to queue job is done            self.in_queue.task_done()class DatamineThread(threading.Thread):    """Threaded Url Grab"""    def __init__(self, out_queue):        threading.Thread.__init__(self)        self.out_queue = out_queue    def run(self):        while True:            #grabs host from queue            chunk = self.out_queue.get()            #parse the chunk            soup = BeautifulSoup(chunk)            print(soup.findAll([‘title‘]))            #signals to queue job is done            self.out_queue.task_done()start = time.time()def main():    #spawn a pool of threads, and pass them queue instance    for i in range(4):        t = ThreadUrl(in_queue, out_queue)        t.setDaemon(True)        t.start()    #populate queue with data    for host in hosts:        in_queue.put(host)    for i in range(4):        dt = DatamineThread(out_queue)        dt.setDaemon(True)        dt.start()    #wait on the queue until everything has been processed    in_queue.join()    out_queue.join()main()print("Elapsed Time: %s" % (time.time() - start))

    代碼解讀

    1. 我們添加了另一個隊列執行個體,然後將該隊列傳遞給第一個線程池類 ThreadURL

    2. 對於另一個線程池類 DatamineThread, 幾乎複製了完全相同的結構。

    3. 在這個類的 run 方法中,從隊列中的各個線程擷取 Web 頁面、文字區塊,然後使用 Beautiful Soup 處理這個文字區塊。

    4. 使用 Beautiful Soup 提取每個頁面的 title 標記、並將其列印輸出。

    5. 可以很容易地將這個樣本推廣到一些更有價值的應用情境,因為您掌握了基本搜尋引擎或者資料採礦工具的核心內容。

    6. 一種思想是使用 Beautiful Soup 從每個頁面中提取連結,然後按照它們進行導航。

 

python線程的使用模式

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.