天天看點

python進階學習筆記(四)--多線程thread

在使用多線程之前,我們首頁要了解什麼是程序和線程。

什麼是程序?

計算機程式隻不過是磁盤中可執行的,二進制(或其它類型)

的資料。它們隻有在被讀取到記憶體中,被作業系統調用的時候才開始它們的生命期。程序(有時被稱為重量級程序)是程式的一次執行。每個程序都有自己的位址空

間,記憶體,資料棧以及其它記錄其運作軌迹的輔助資料。作業系統管理在其上運作的所有程序,并為這些程序公平地配置設定時間。

什麼是線程?

線程(有時被稱為輕量級程序)跟程序有些相似,不同的是,所有的線程運作在同一個程序中,共享相同的運作環境。我們可以想像成是在主程序或“主線程”中并行運作的“迷你程序”。

  在單線程中順序執行兩個循環。一定要一個循環結束後,另一個才能開始。總時間是各個循環運作時間之和。 

onetherad.py

運作結果:

  python通過兩個标準庫thread和threading提供對線程的支援。thread提供了低級别的、原始的線程以及一個簡單的鎖。threading基于java的線程模型設計。鎖(lock)和條件變量(condition)在java中是對象的基本行為(每一個對象都自帶了鎖和條件變量),而在python中則是獨立的對象。

mtsleep1.py

  start_new_thread()要求一定要有前兩個參數。是以,就算我們想要運作的函數不要參數,我們也要傳一個空的元組。 

  這個程式的輸出與之前的輸出大不相同,之前是運作了 6,7 秒,而現在則是 4 秒,是最長的循環的運作時間與其它的代碼的時間總和。

   睡眠 4 秒和 2 秒的代碼現在是并發執行的。這樣,就使得總的運作時間被縮短了。你可以看到,loop1 甚至在 loop0 前面就結束了。

  程式的一大不同之處就是多了一個“sleep(6)”的函數調用。如果我們沒有讓主線程停下來,那主線程就會運作下一條語句,顯示“all end”,然後就關閉運作着 loop0()和 loop1()的兩個線程并退出了。我們使用 6 秒是因為我們已經知道,兩個線程(你知道,一個要 4 秒,一個要 2 秒)在主線程等待 6 秒後應該已經結束了。

  你也許在想,應該有什麼好的管理線程的方法,而不是在主線程裡做一個額外的延時 6 秒的操作。因為這樣一來,我們的總的運作時間并不比單線程的版本來得少。而且,像這樣使用 sleep()函數做線程的同步操作是不可靠的。如果我們的循環的執行時間不能事先确定的話,那怎麼辦呢?這可能造成主線程過早或過晚退出。這就是鎖的用武之地了。

mtsleep2.py

thread.allocate_lock() 

  傳回一個新的鎖定對象。

acquire() /release() 

  一個原始的鎖有兩種狀态,鎖定與解鎖,分别對應acquire()和release() 方法。

range()

  range()函數來建立清單包含算術級數。

range(len(loops))了解:

  我們先調用 thread.allocate_lock()函數建立一個鎖的清單,并分别調用各個鎖的 acquire()函數獲得鎖。獲得鎖表示“把鎖鎖上”。鎖上後,我們就把鎖放到鎖清單 locks 中。

  下一個循環建立線程,每個線程都用各自的循環号,睡眠時間和鎖為參數去調用 loop()函數。為什麼我們不在建立鎖的循環裡建立線程呢?有以下幾個原因:(1) 我們想到實作線程的同步,是以要讓“所有的馬同時沖出栅欄”。(2) 擷取鎖要花一些時間,如果你的線程退出得“太快”,可能會導緻還沒有獲得鎖,線程就已經結束了的情況。

  線上程結束的時候,線程要自己去做解鎖操作。最後一個循環隻是坐在那一直等(達到暫停主線程的目的),直到兩個鎖都被解鎖為止才繼續運作。

mtsleep2.py運作結果:

  我們應該避免使用thread子產品,原因是它不支援守護線程。當主線程退出時,所有的子線程不論它們是否還在工作,都會被強行退出。有時我們并不期望這種行為,這時就引入了守護線程的概念。threading子產品則支援守護線程。

mtsleep3.py

start()

  開始線程活動

join()

  等待線程終止

  所有的線程都建立了之後,再一起調用 start()函數啟動,而不是建立一個啟動一個。而且,不用再管理一堆鎖(配置設定鎖,獲得鎖,釋放鎖,檢查鎖的狀态等),隻要簡單地對每個線程調用 join()函數就可以了。

join()會等到線程結束,或者在給了 timeout 參數的時候,等到逾時為止。join()的另一個比較重要的方面是它可以完全不用調用。一旦線程啟動後,就會一直運作,直到線程的函數結束,退出為止。

使用可調用的類

 mtsleep4.py

 運作結果:

建立新線程的時候,thread 對象會調用我們的threadfunc 對象,這時會用到一個特殊函數__call__()。由于我們已經有了要用的參數,是以就不用再傳到 thread()的構造函數中。由于我們有一個參數的元組,這時要在代碼中使用 apply()函數。

我們傳了一個可調用的類(的執行個體),而不是僅傳一個函數。

__init__()

方法在類的一個對象被建立時運作。這個方法可以用來對你的對象做一些初始化。

apply()

apply(func [, args [, kwargs ]]) 函數用于當函數參數已經存在于一個元組或字典中時,間接地調用函數。args是一個包含将要提供給函數的按位置傳遞的參數的元組。如果省略了args,任何參數都不會被傳遞,kwargs是一個包含關鍵字參數的字典。

apply() 用法: