為了解決阻塞(如I/O)問題,我們需要對程式進行并發設計。
本文将通過将線程和隊列 結合在一起,輕松地在 Python 中完成線程程式設計,建立一些簡單但有效的線程使用模式。
先看一個線程不多的例子,不存在阻塞,很簡單:
<a></a>
代碼解讀
1. 兩個線程都輸出了 Hello World 語句,并都帶有日期戳。
2. 兩個導入語句:一個導入了日期時間子產品,另一個導入線程子產品。
3. 類 My<code>Thread</code> 繼承自 <code>threading.Thread</code>,也正因為如此,您需要定義一個 run 方法,以此執行您在該線程中要運作的代碼。
4. run 方法中的<code>self.getName()</code> 是一個用于确定該線程名稱的方法。
5. 最後三行代碼實際地調用該類,并啟動線程。如果注意的話,那麼會發現實際啟動線程的是 <code>t.start()</code>。
如前所述,當多個線程需要共享資料或者資源的時候,可能會使得線程的使用變得複雜。線程子產品提供了許多同步原語,包括信号量、條件變量、事件和鎖。當這些 選項存在時,最佳實踐是轉而關注于使用隊列。相比較而言,隊列更容易處理,并且可以使得線程程式設計更加安全,因為它們能夠有效地傳送單個線程對資源的所有訪 問,并支援更加清晰的、可讀性更強的設計模式。
在下一個示例中,我們的目的是:擷取網站的 URL,并顯示頁面的前 300 個位元組。
先看看串行方式或者依次執行實作的代碼:
1. <code>urllib</code> 子產品減少了擷取 Web 頁面的複雜程度。兩次 time.time() 用于計算程式運作時間。
2. 這個程式的執行速度是 13.7 秒,這個結果并不算太好,也不算太糟。
3. 但如果需要檢索數百個 Web 頁面,那麼按照這個平均值,總時間需要花費大約 1000 秒的時間。如果需要檢索更多頁面呢?
下面給出線程化版本:
1. 與第一個線程示例相比,它并沒有複雜多少,因為使用了隊列子產品。
2. 建立一個 <code>queue.Queue()</code> 的執行個體,然後使用資料對它進行填充。
3. 将經過填充資料的執行個體傳遞給線程類,後者是通過繼承 <code>threading.Thread</code> 的方式建立的。
4. 生成守護線程池。
5. 每次從隊列中取出一個項目,并使用該線程中的資料和 run 方法以執行相應的工作。
6. 在完成這項工作之後,使用 <code>queue.task_done()</code> 函數向任務已經完成的隊列發送一個信号。
7. 對隊列執行 join 操作,實際上意味着等到隊列為空,再退出主程式。
在使用這個模式時需要注意一點:通過将守護線程設定為 true,将允許主線程或者程式僅在守護線程處于活動狀态時才能夠退出。這種方式建立了一種簡單的方式以控制程式流程,因為在退出之前,您可以對隊列執行 join 操作、或者等到隊列為空。
<code>join()</code>保持阻塞狀态,直到處理了隊列中的所有項目為止。在将一個項目添加到該隊列時,未完成的任務的總數就會增加。當使用者線程調用 task_done() 以表示檢索了該項目、并完成了所有的工作時,那麼未完成的任務的總數就會減少。當未完成的任務的總數減少到零時,<code>join()</code> 就會結束阻塞狀态。
下一個示例有兩個隊列。其中一個隊列的各線程擷取的完整 Web 頁面,然後将結果放置到第二個隊列中。然後,對加入到第二個隊列中的另一個線程池進行設定,然後對 Web 頁面執行相應的處理。
提取所通路的每個頁面的 title 标記,并将其列印輸出。
1. 我們添加了另一個隊列執行個體,然後将該隊列傳遞給第一個線程池類 <code>ThreadURL</code>。
2. 對于另一個線程池類 <code>DatamineThread</code>, 幾乎複制了完全相同的結構。
3. 在這個類的 run 方法中,從隊列中的各個線程擷取 Web 頁面、文本塊,然後使用 Beautiful Soup 處理這個文本塊。
4. 使用 Beautiful Soup 提取每個頁面的 title 标記、并将其列印輸出。
5. 可以很容易地将這個示例推廣到一些更有價值的應用場景,因為您掌握了基本搜尋引擎或者資料挖掘工具的核心内容。
6. 一種思想是使用 Beautiful Soup 從每個頁面中提取連結,然後按照它們進行導航。
本文轉自羅兵部落格園部落格,原文連結:http://www.cnblogs.com/hhh5460/p/4414188.html,如需轉載請自行聯系原作者