python 執行緒筆記(一)

這道題會做發表於2012-11-03

1、一個順序執行的程式要從每個I/O終端通道檢查使用者的輸入時,程式無論如何也不能在讀取I/O終端通道的時候阻塞,因為使用者輸入的到達是不確定的。

阻塞會導致其他I/O資訊的資料部能被處理,順序執行的程式必須使用非阻塞I/O,或是帶有計時器的阻塞I/O(這樣才能保證
阻塞只是暫時的)。  


2、  使用多執行緒程式設計和一個共享的資料結構如Queue (本章後面會介紹的一種多執行緒佇列資料結構),
這種程式任務可以用幾個功能單一的執行緒來組織:  
      UserRequestThread: 負責讀取客戶的輸入,可能是一個I/O通道。程式可能建立多個執行緒,  每個客戶一個,請求會被放入佇列中。  
     RequestProcessor: 一個負責從佇列中獲取並處理請求的執行緒,它為下面那種執行緒提供輸出。  
     ReplyThread: 負責把給使用者的輸出取出來,如果是網路應用程式就把結果傳送出去,否則就儲存到本地檔案系統或資料庫中。  


3、
計算機程式只不過是磁碟中可執行的,二進位制(或其它型別)的資料。它們只有在被讀取到記憶體中,被作業系統呼叫的時候才開始它們的生命期。程式 (有時被稱為重量級程式)是程式的一次執行。每個程式都有自己的地址空間,記憶體,資料棧以及其它記錄其執行軌跡的輔助資料。作業系統管理在其上執行的所有程式,併為這些程式公平地分配時間。程式也可以通過fork和spawn操作 來完成其它的任務。不過各個程式有自己的記憶體空間,資料棧等,所以只能使用程式間通訊(IPC), 而不能直接共享資訊。  


4、 執行緒有開始,順序執行和結束三部分。它有一個自己的指令指標,記錄自己執行到什麼地方。執行緒的執行可能被搶佔(中斷),或暫時的被掛起(也叫睡眠),讓其它的執行緒執行,這叫做讓步。 一個程式中的各個執行緒之間共享同一片資料空間,所以執行緒之間可以比程式之間更方便地共享資料 以及相互通訊。執行緒一般都是併發執行的,正是由於這種並行和資料共享的機制使得多個任務的合 作變為可能。實際上,在單 CPU 的系統中,真正的併發是不可能的,每個執行緒會被安排成每次只運
行一小會,然後就把 CPU 讓出來,讓其它的執行緒去執行。在程式的整個執行過程中,每個執行緒都只 做自己的事,在需要的時候跟其它的執行緒共享執行的結果。  
       當然,這樣的共享並不是完全沒有危險的。如果多個執行緒共同訪問同一片資料,則由於資料訪 問的順序不一樣,有可能導致資料結果的不一致的問題。這叫做競態條件(race condition)。幸運的是,大多數執行緒庫都帶有一系列的同步原語,來控制執行緒的執行和資料的訪問。  
       另一個要注意的地方是,由於有的函式會在完成之前阻塞住,在沒有特別為多執行緒做修改的情況下,這種“貪婪”的函式會讓 CPU 的時間分配有所傾斜。導致各個執行緒分配到的執行時間可能不盡相同,不盡公平。  

5、 對 Python 虛擬機器的訪問由全域性直譯器鎖(GIL)來控制,正是這個鎖能保證同一時刻只有一個執行緒在執行。在多執行緒環境中,Python虛擬機器按以下方式執行:  
      
    1.   設定GIL  
    2.   切換到一個執行緒去執行  
    3.   執行:  
      a. 指定數量的位元組碼指令,或者  
      b. 執行緒主動讓出控制(可以呼叫time.sleep(0))  
    4.   把執行緒設定為睡眠狀態  
    5.   解鎖GIL  
    6.   再次重複以上所有步驟 
6、 當一個執行緒結束計算,它就退出了。執行緒可以呼叫thread.exit()之類的退出函式,也可以使用Python 退出程式的標準方法,如 sys.exit()或丟擲一個 SystemExit 異常等。不過,你不可以直接 “殺掉”("kill")一個執行緒。  

7、 核心提示:避免使用 thread 模組  
    出於以下幾點考慮,我們不建議您使用 thread 模組。首先,更高階別的 threading 模組更為先進,對執行緒的支援更為完善,而且使用 thread 模組裡的屬性有可能會與 threading 出現衝突。其次,低階別的 thread 模組的同步原語很少(實際上只有一個),而 threading 模組則有很多。  

8、thread模組和鎖物件  
      
    函式                               描述  
    thread模組函式  
    start_new_thread(function,   args, kwargs=None)              產生一個新的執行緒,在新執行緒中用指定的引數和可選的
                                  kwargs來呼叫這個函式。  
    allocate_lock()                  分配一個LockType型別的鎖物件  
    exit()                          讓執行緒退出  


    LockType型別鎖物件方法  
    acquire(wait=None)              嘗試獲取鎖物件  
    locked()                        如果獲取了鎖物件返回True,否則返回False  
    release()                         釋放鎖 

9、使用執行緒和鎖
      
      
  

import thread  
from time import sleep, ctime  
  
loops = [4,2]  
  
def loop(nloop, nsec, lock):
    print 'start loop', nloop, 'at:', ctime()  
    sleep(nsec)  
    print 'loop', nloop, 'done at:', ctime()  
    lock.release()  

def main():  
    print 'starting at:', ctime()  
    locks = []  
    nloops = range(len(loops))  
       
    for i in nloops:  
        lock = thread.allocate_lock()  
        lock.acquire()  
        locks.append(lock)  
  
    for i in nloops:  
        thread.start_new_thread(loop, (i, loops[i], locks[i]))  
      
    for i in nloops:  
        while locks[i].locked(): pass  
    
    print 'all DONE at:', ctime()  
if __name__ == "__main__":
    main() 

執行結果:

>>> starting at: Sat Nov 03 20:02:34 2012
start loop 0 at: Sat Nov 03 20:02:34 2012
start loop 1 at: Sat Nov 03 20:02:34 2012
loop 1 done at: Sat Nov 03 20:02:36 2012
loop 0 done at: Sat Nov 03 20:02:38 2012
all DONE at: Sat Nov 03 20:02:38 2012

主要的工作在包含三個迴圈的main()函式中完成。我們先呼叫thread.allocate_lock()函式建立一個鎖的列表,並分別呼叫各個鎖的 acquire()函式獲得鎖。獲得鎖表示“把鎖鎖上”。鎖上後,我們就把鎖放到鎖列表 locks 中。下一個迴圈建立執行緒,每個執行緒都用各自的迴圈號,睡眠時間和鎖為引數去呼叫loop()函式。
線上程結束的時候,執行緒要自己去做解鎖操作。最後一個迴圈只是坐在那一直等(達到暫停主執行緒的目的),直到兩個鎖都被解鎖為止才繼續執行。由於我們順序檢查每一個鎖,所以我們可能會要長時間地等待執行時間長且放在前面的執行緒,當這些執行緒的鎖釋放之後,後面的鎖可能早就釋放了(表示對應的執行緒已經執行完了)。結果主執行緒只能毫不停歇地完成對後面這些鎖的檢查。

相關文章