萬盛學電腦網

 萬盛學電腦網 >> 網絡編程 >> 編程語言綜合 >> 在Python下嘗試多線程編程

在Python下嘗試多線程編程

   這篇文章主要介紹了在Python下多線程編程的嘗試,由於GIL的存在,多線程在Python開發領域一直是個熱門問題,需要的朋友可以參考下

  多任務可以由多進程完成,也可以由一個進程內的多線程完成。

  我們前面提到了進程是由若干線程組成的,一個進程至少有一個線程。

  由於線程是操作系統直接支持的執行單元,因此,高級語言通常都內置多線程的支持,Python也不例外,並且,Python的線程是真正的Posix Thread,而不是模擬出來的線程。

  Python的標准庫提供了兩個模塊:thread和threading,thread是低級模塊,threading是高級模塊,對thread進行了封裝。絕大多數情況下,我們只需要使用threading這個高級模塊。

  啟動一個線程就是把一個函數傳入並創建Thread實例,然後調用start()開始執行:

  ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import time, threading   # 新線程執行的代碼: def loop(): print 'thread %s is running...' % threading.current_thread().name n = 0 while n < 5: n = n + 1 print 'thread %s >>> %s' % (threading.current_thread().name, n) time.sleep(1) print 'thread %s ended.' % threading.current_thread().name   print 'thread %s is running...' % threading.current_thread().name t = threading.Thread(target=loop, name='LoopThread') t.start() t.join() print 'thread %s ended.' % threading.current_thread().name

  執行結果如下:

  ?

1 2 3 4 5 6 7 8 9 thread MainThread is running... thread LoopThread is running... thread LoopThread >>> 1 thread LoopThread >>> 2 thread LoopThread >>> 3 thread LoopThread >>> 4 thread LoopThread >>> 5 thread LoopThread ended. thread MainThread ended.

  由於任何進程默認就會啟動一個線程,我們把該線程稱為主線程,主線程又可以啟動新的線程,Python的threading模塊有個current_thread()函數,它永遠返回當前線程的實例。主線程實例的名字叫MainThread,子線程的名字在創建時指定,我們用LoopThread命名子線程。名字僅僅在打印時用來顯示,完全沒有其他意義,如果不起名字Python就自動給線程命名為Thread-1,Thread-2……

  Lock

  多線程和多進程最大的不同在於,多進程中,同一個變量,各自有一份拷貝存在於每個進程中,互不影響,而多線程中,所有變量都由所有線程共享,所以,任何一個變量都可以被任何一個線程修改,因此,線程之間共享數據最大的危險在於多個線程同時改一個變量,把內容給改亂了。

  來看看多個線程同時操作一個變量怎麼把內容給改亂了:

  ?

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import time, threading     # 假定這是你的銀行存款: balance = 0   def change_it(n): # 先存後取,結果應該為0: global balance balance = balance + n balance = balance - n   def run_thread(n): for i in range(100000): change_it(n)   t1 = threading.Thread(target=run_thread, args=(5,)) t2 = threading.Thread(target=run_thread, args=(8,)) t1.start() t2.start() t1.join() t2.join() print balance

  我們定義了一個共享變量balance,初始值為0,並且啟動兩個線程,先存後取,理論上結果應該為0,但是,由於線程的調度是由操作系統決定的,當t1、t2交替執行時,只要循環次數足夠多,balance的結果就不一定是0了。

  原因是因為高級語言的一條語句在CPU執行時是若干條語句,即使一個簡單的計算:

  ?

1 balance = balance + n

  也分兩步:

  計算balance + n,存入臨時變量中;

  將臨時變量的值賦給balance。

  也就是可以看成:

  ?

1 2 x = balance + n balance = x

  由於x是局部變量,兩個線程各自都有自己的x,當代碼正常執行時:

  初始值 balance = 0

  ?

1 2 3 4 5 6 7 8 9 t1: x1 = balance + 5 # x1 = 0 + 5 = 5 t1: balance = x1 # balance = 5 t1: x1 = balance - 5 # x1 = 5 - 5 = 0 t1: balance = x1 # balance = 0   t2: x2 = balance + 8 # x2 = 0 + 8 = 8 t2: balance = x2 # balance = 8 t2: x2 = balance - 8 # x2 = 8 - 8 = 0 t2: balance = x2 # balance = 0

  結果 balance = 0

  但是t1和t2是交替運行的,如果操作系統以下面的順序執行t1、t2:

  初始值 balance = 0

  ?

1 2 3 4 5 6 7 8 9 10 11 t1: x1 = balance + 5 # x1 = 0 + 5 = 5   t2: x2 = balance + 8 # x2 = 0 + 8 = 8 t2: balance = x2 # balance = 8   t1: balance = x1 # balance = 5 t1: x1 = balance - 5 # x1 = 5 - 5 = 0 t1: balance = x1 # balance = 0   t2: x2 = balance - 5 # x2 = 0 - 5 = -5 t2: balance = x2 # balance = -5

  結果 balance = -5

  究其原因,是因為修改balance需要多條語句,而執行這幾條語句時,線程可能中斷,從而導致多個線程把同一個對象的內容改亂了。

  兩個線程同時一存一取,就可能導致余額不對,你肯定不希望你的銀行存款莫名其妙地變成了負數,所以,我們必須確保一個線程在修改balance的時候,別的線程一定不能改。

  如果我們要確保balance計算正確,就要給change_it()上一把鎖,當某個線程開始執行change_it()時,我們說,該線程因為獲得了鎖,因此其他線程不能同時執行change_it(),只能等待,直到鎖被釋放後,獲得該鎖以後才能改。由於鎖只有一個,無論多少線程,同一時刻最多只有一個線程持有該鎖,所以,不會造成修改的沖突。創建一個鎖就是通過threading.Lock()來實現:

  ?

1 2 3 4 5 6 7 8 9 10 11 12 13 balance = 0 lock = threading.Lock()  
copyright © 萬盛學電腦網 all rights reserved