06.22 Python 模塊 線程 Threading

Python 模塊 線程 Threading

使用線程(threading),可以在一個進程(process)中,同時進行多個操作。

使用線程對象(Thread Objects)


最簡單的使用線程的方法就是,實例化線程,傳遞一個 target 函數,然後調用 start() 方法開始執行。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

以上生成了3個線程,使用 target 參數指定了線程要運行的函數 worker,3個線程同時運行了函數。

向線程傳遞參數


可以在生成線程的時候,給線程傳遞參數,告訴它工作時的一些信息。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

這個例子向線程傳遞了參數,通過 args 參數,以元組的形式傳入到工作線程中。worker 函數按順序接收到了參數。

給線程命名


生成線程的時候,可以通過參數 name 給線程指定一個名稱。在線程運行的時候,可以通過名稱判斷運行的是哪個線程。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

輸出打印了線程的名稱。是通過 current_thread() 方法獲取到當前線程,再調用線程的 getName() 方法獲取到線程名稱。如果不指定 name 參數,會提供一個默認名稱。

後臺線程(Daemon Threads)


上面的例子中,都需要等待線程完成工作,主程序才會退出。有時候,需要把線程放到後臺執行,而不干擾主程序的退出。

要生成一個後臺線程,需要傳遞參數 daemon=True 或者調用方法 setDaemon()。默認生成的不是後臺線程。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

可見,後臺線程 worker() 函數最後一行沒有打印,主程序就退出了。

如果要等待一個線程執行完後,才繼續執行主程序,需要調用 join() 方法

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

兩個線程都完成了自己的工作,全都打印了信息。

默認 join() 方法會一直等待線程結束,你也可以傳遞一個超時值,如果超時,就算線程沒有完成工作,也不會再等待了。

如果我們把上例改成 t.join(0.1),則輸出為:

Python 模塊 線程 Threading

因為後臺線程要休眠1秒,t.join(0.1) 超時值 0.1 秒,顯然不會等待它執行完,所以主程序很快退出了。

線程子類


另一種運行線程的方法是,定義一個類,繼承自線程基類 Thread。然後定義一個 run() 方法:

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

實例化對象後,直接調用 start() 方法,啟動線程。

向線程子類傳遞參數


Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

類 A 實現了繼承的 Thread 相同的接口,可以傳遞參數如:組 group,目標函數 target,線程名稱 name 和 把線程放入後臺的 daemon。

這裡使用了 args 和 kwargs 傳遞信息,你也可以自定義傳入哪些參數,最後不要忘了調用 父類 super().__init__() 方法。

線程 Timer


線程模塊 threading 提供類 Timer,允許隔一段時間後,再開始運行線程。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

線程 t = threading.Timer(1, worker) 1秒後開始運行,另一個 t2 2秒後開始運行。

可以通過 cancel() 方法取消線程運行。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

主程序在 1.2 秒的時候,調用 cancel() 方法取消了線程 t2, 因為 t2 2秒後才開始執行,所以沒有輸出 t2 的信息。

線程間的信號(Signal)


雖然線程是各自併發的執行,但是有時候在線程間需要同步(synchronize)一些操作。使用 Event 對象是一個簡單的方式,它使用內部的標誌位,可以通過 set() 和 clear() 設置和取消,其他線程可以使用 wait() 判斷,直到標誌位設置成功,線程才會繼續執行。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

當主程序 Event 調用 set() 方法後,兩個線程才開始執行。

wait() 方法,接收一個參數:要等待的秒數,返回的是一個 Boolean 類型,告訴我們事件對象 Event 裡的標誌位是否設置過了。還可以通過 is_set() 函數判斷是否設置過了,它不會阻塞。

worker_timeout() 函數會重複判斷是否設置了標誌位,直到設置了才會退出,例如我們更改主程序休眠的時間:

Python 模塊 線程 Threading

返回的是:

Python 模塊 線程 Threading

第一次調用 wait(2) 2秒的時候,還沒有設置,所以打印 event is set: False,然後繼續循環,第二次設置成功。

控制資源訪問


多線程的程序中,很重要的是控制如何訪問共享的數據。Python 內置的數據結構(built-in data structures)如:列表(Lists)、字典(Dictionaries)等都是線程安全的(thread-safe),因為有全局解釋器鎖(Global Interpreter Lock)保護他們不會在一個線程訪問的時候另一個線程操作數據。其他的數據結構如:整形、浮點型等都缺乏這樣的保護。

為了保護一個對象不會被多個線程同時訪問,使用 Lock 對象。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

這個例子中,worker() 函數傳入計數器對象,循環3次,計數器調用方法 incr()自增3次。每次調用 incr() 時,調用 time.sleep() 方法,這樣允許切換其他線程,讓他們交替執行計數器自增方法。

incr() 每次進行自增操作時,都調用 acquire() 獲取鎖,然後使用 try finally 語句確保發生任何情況下都釋放鎖。

一共開啟了3個線程,每個線程分別執行3次計數器對象的自增方法,最後返回正確的值:9。

最後遍歷線程,使用了 threading.enumerate() 方法,排除了主線程,每個子線程調用 join() 方法,等待他們執行完畢。

另外,acquire() 默認會阻塞當前線程。你可以傳遞參數 False 或者 0,這樣調用時,不會阻塞當前線程,它返回當前線程是否獲取到了鎖,根據返回值,做想要的操作。

Re-entrant Lock

正常的鎖不能獲取(acquire)多次,即使在同一個線程裡:

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

第二次調用 acquire() 傳入0是為了不阻塞當前主線程,因為鎖已經讓第一個語句獲取了。

想要重新獲取鎖,只需要使用 RLock 替代。

Python 模塊 線程 Threading

Lock 上下文管理(Context Managers)


鎖 Locks 實現了上下文接口,兼容 with 語句。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

使用 with 語句,會自動獲取和釋放鎖,它和使用 try finally 效果是一樣的。

對象 Condition 同步線程


除了使用 Event 對象,還可以使用 Condition 對象同步線程,因為它使用了 Lock 鎖對象,在多個線程訪問同一個資源時會等待資源更新完畢。

下面的例子,實現了生產者-消費者模式(producer-consumer),消費者(consumer)等待狀態改變才會執行。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

這個例子,生成了一個生產者線程 producer 和兩個消費者線程 consumer,消費者一開始調用 Condition 對象的 wait() 方法等待,知道生產者 producer 開始執行,使用 notifyAll() 方法更改了 Condition 對象的狀態,才開始執行。

threading 還提供一個類 Barrier ,它接收一個數量,只有當線程 wait() 到這個數量時,所有的線程才會執行。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

這個例子中,Barrier 對象在等待的數量達到3之前會一直阻塞,然後所有的線程會一起執行。 wait() 函數返回的是釋放的數量,你可以根據返回的數量,清理一些資源。

使用 Barrier 的 abort() 方法,會使所有的線程拋出 BrokenBarrierError異常,可以在 wait() 方法上捕獲這個異常,清理資源。

限制資源的訪問


有時候,允許多個線程同時訪問某一資源,只是限制一下總的線程數量。例如,一個連接池支持一定數量的併發連接,或者一個網絡應用支持一定的併發下載。Semaphore就是用來處理這種情況的。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

這個例子中,ActivePool 只是用來跟蹤某一時刻活動的線程。在創建 Semaphore的時候,限制了最多能同時運行2個線程,從輸出可以看到這個結果。

線程自己的數據


除了需要同步訪問共享的數據外,有時候線程還需要有自己的數據,這部分數據對外部來說是隱藏的,外部無法訪問。可以使用 local() 方法或者自定義類繼承 local。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

可以看到,雖然傳遞都是變量 data ,但是在每個線程中初始值都沒有屬性 value。

為了初始化屬性 value,可以使用類繼承自 local ,然後在 __init__() 初始化數據。

Python 模塊 線程 Threading

執行:

Python 模塊 線程 Threading

在每個線程,__init__ 都會首先執行,初始化 value 為100。


分享到:


相關文章: