多線程
-
什麼是多線程
多線程類似於同時執行多個不同程序,多線程運行有如下優點:
- 使用線程可以把佔據長時間的程序中的任務放到後臺去處理。
- 用戶界面可以更加吸引人,這樣比如用戶點擊了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度
- 程序的運行速度可能加快
- 在一些等待的任務實現上如用戶輸入、文件讀寫和網絡收發數據等,線程就比較有用了。在這種情況下我們可以釋放一些珍貴的資源如內存佔用等等。
每個線程都有他自己的一組CPU寄存器,稱爲線程的上下文,該上下文反映了線程上次運行該線程的CPU寄存器的狀態。
指令指針和堆棧指針寄存器是線程上下文中兩個最重要的寄存器,線程總是在進程得到上下文中運行的,這些地址都用於標誌擁有線程的進程地址空間中的內存。Python的標準庫提供了兩個模塊:_thread和threading,_thread是低級模塊,threading是高級模塊,對_thread進行了封裝。絕大多數情況下,我們只需要使用threading這個高級模塊。
由於任何進程默認就會啓動一個線程,我們把該線程稱爲主線程,主線程又可以啓動新的線程,Python的threading模塊有個current_thread()函數,它永遠返回當前線程的實例。主線程實例的名字叫MainThread,子線程的名字在創建時指定,如果不起名字Python就自動給線程命名爲Thread-1,Thread-2……
threading用於提供線程相關的操作,線程是應用程序中工作的最小單元。python當前版本的多線程庫沒有實現優先級、線程組,線程也不能被停止、暫停、恢復、中斷。
threading模塊提供的類:
Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local。threading 模塊提供的常量:
threading.TIMEOUT_MAX 設置threading全局超時時間。
-
開啓線程的兩種方式-函數
import threading import time #方法一:將要執行的方法作爲參數傳給Thread的構造方法 def action(arg): time.sleep(1) print(threading.current_thread()) print('the arg is:{0}'.format(arg)) for i in range(4): t =threading.Thread(target=action,args=(i,)) t.start() if __name__ == '__main__': print(threading.current_thread()) print('main thread end!')
-
開啓線程的兩種方式-用類來包裝線程對象
class MyThread(threading.Thread): def __init__(self,arg): super(MyThread, self).__init__()#注意:一定要顯式的調用父類的初始化函數。 self.arg=arg def run(self):#定義每個線程要運行的函數 time.sleep(1) print 'the arg is:%s\r' % self.arg for i in xrange(4): t =MyThread(i) t.start()
Thread類
構造方法:
-
Thread(group=None, target=None, name=None, args=(), kwargs={})
-
group: 線程組,目前還沒有實現,庫引用中提示必須是None;
-
target: 要執行的方法;
-
name: 線程名;
-
args/kwargs: 要傳入方法的參數。
實例方法:
-
isAlive(): 返回線程是否在運行。正在運行指啓動後、終止前。
-
get/setName(name): 獲取/設置線程名。
-
start(): 線程準備就緒,等待CPU調度
start(): 啓動線程。
join([timeout]): 阻塞當前上下文環境的線程,直到調用此方法的線程終止或到達指定的timeout(可選參數)。
-
-
threading 模塊提供的常用方法:
- threading.currentThread(): 返回當前的線程變量。
- threading.enumerate(): 返回一個包含正在運行的線程的list。正在運行指線程啓動後、結束前,不包括啓動前和終止後的線程。
- threading.activeCount(): 返回正在運行的線程數量,與len(threading.enumerate())有相同的結果。
-
後臺線程和前臺線程
is/setDaemon(bool): 獲取/設置是後臺線程(默認前臺線程(False))。(在start之前設置)
-
如果是後臺線程,主線程執行過程中,後臺線程也在進行,主線程執行完畢後,後臺線程不論成功與否,主線程和後臺線程均停止
-
如果是前臺線程,主線程執行過程中,前臺線程也在進行,主線程執行完畢後,等待前臺線程也執行完成後,程序停止
if __name__ == '__main__': for i in range(4): t = MyThread(i) t.setDaemon(True) t.start() print('main thread end!')
-
-
join()
join()阻塞當前上下文環境的線程,直到調用此方法的線程終止或到達指定的timeout,即使設置了setDeamon(True)主線程依然要等待子線程結束。
線程必須先start()然後再join()
錯誤的做法是
if __name__ == '__main__': for i in range(4): t = MyThread(i) t.start() t.join()
可以看出此時,程序只能順序執行,每個線程都被上一個線程的join阻塞,使得“多線程”失去了多線程意義。
☝️這樣
if __name__ == '__main__': th=[] for i in range(4): t = MyThread(i) th.append(t) t.start() for tt in th: tt.join() #設置join之後,主線程等待子線程全部執行完成後或者子線程超時後,主線程才結束 print('main thread end!')
-
線程同步-Lock、Rlock類
由於線程之間隨機調度:某線程可能在執行n條後,CPU接着執行其他線程。爲了多個線程同時操作一個內存中的資源時不產生混亂,我們使用鎖。
Lock(指令鎖)是可用的最低級的同步指令。Lock處於鎖定狀態時,不被特定的線程擁有。Lock包含兩種狀態——鎖定和非鎖定,以及兩個基本的方法。
可以認爲Lock有一個鎖定池,當線程請求鎖定時,將線程至於池中,直到獲得鎖定後出池。池中的線程處於狀態圖中的同步阻塞狀態。
RLock(可重入鎖)是一個可以被同一個線程請求多次的同步指令。RLock使用了“擁有的線程”和“遞歸等級”的概念,處於鎖定狀態時,RLock被某個線程擁有。擁有RLock的線程可以再次調用acquire(),釋放鎖時需要調用release()相同次數。
可以認爲RLock包含一個鎖定池和一個初始值爲0的計數器,每次成功調用 acquire()/release(),計數器將+1/-1,爲0時鎖處於未鎖定狀態。
實例方法:
-
acquire([timeout]): 嘗試獲得鎖定。使線程進入同步阻塞狀態。
-
release(): 釋放鎖。使用前線程必須已獲得鎖定,否則將拋出異常。
import threading import time # 方法一:將要執行的方法作爲參數傳給Thread的構造方法 count=0 lock = threading.RLock() def action(arg): lock.acquire() time.sleep(1) global count count+=1 print(threading.current_thread()) count-=1 print('the arg is:{0},count is:{1}'.format(arg,count)) lock.release() ths=[] for i in range(4): t =threading.Thread(target=action,args=(i,)) # t.setDaemon(True) ths.append(t) for tt in ths: tt.start() for tt in ths: tt.join() if __name__ == '__main__': # print(threading.current_thread()) # print(threading.enumerate()) # print(threading.activeCount()) print('main thread end!')
-
-
Lock對比Rlock
import threading lock = threading.Lock() #Lock對象 lock.acquire() lock.acquire() #產生了死鎖。 lock.release() lock.release() print lock.acquire() import threading rLock = threading.RLock() #RLock對象 rLock.acquire() rLock.acquire() #在同一線程內,程序不會堵塞。 rLock.release() rLock.release()
-
Condition類
Condition(條件變量)通常與一個鎖關聯。需要在多個Contidion中共享一個鎖時,可以傳遞一個Lock/RLock實例給構造方法,否則它將自己生成一個RLock實例。
可以認爲,除了Lock帶有的鎖定池外,Condition還包含一個等待池,池中的線程處於等待阻塞狀態,直到另一個線程調用notify()/notifyAll()通知;得到通知後線程進入鎖定池等待鎖定。
構造方法:
Condition([lock/rlock])實例方法:
-
acquire([timeout])/release(): 調用關聯的鎖的相應方法。
-
wait([timeout]): 調用這個方法將使線程進入Condition的等待池等待通知,並釋放鎖。使用前線程必須已獲得鎖定,否則將拋出異常。
-
notify(): 調用這個方法將從等待池挑選一個線程並通知,收到通知的線程將自動調用acquire()嘗試獲得鎖定(進入鎖定池);其他線程仍然在等待池中。調用這個方法不會釋放鎖定。使用前線程必須已獲得鎖定,否則將拋出異常。
-
notifyAll(): 調用這個方法將通知等待池中所有的線程,這些線程都將進入鎖定池嘗試獲得鎖定。調用這個方法不會釋放鎖定。使用前線程必須已獲得鎖定,否則將拋出異常。
# encoding: UTF-8 import threading import time # 商品 product = None # 條件變量 con = threading.Condition() # 生產者方法 def produce(): global product if con.acquire(): while True: if product is None: print ('produce...') product = 'anything' # 通知消費者,商品已經生產 con.notify() # 等待通知 # con.wait() time.sleep(2) # 消費者方法 def consume(): global product if con.acquire(): while True: if product is not None: print('consume...') product = None # 通知生產者,商品已經沒了 con.notify() # 等待通知 con.wait() time.sleep(2) t1 = threading.Thread(target=produce) t2 = threading.Thread(target=consume) t2.start() t1.start()
生產者消費者模型
import threading import time condition = threading.Condition() products = 0 class Producer(threading.Thread): def run(self): global products while True: if condition.acquire(): if products < 10: products += 1 print("Producer(%s):deliver one, now products:%s" %(self.name, products)) condition.notify()#不釋放鎖定,因此需要下面一句 condition.release() else: print("Producer(%s):already 10, stop deliver, now products:%s" %(self.name, products)) condition.wait()#自動釋放鎖定 time.sleep(2) class Consumer(threading.Thread): def run(self): global products while True: if condition.acquire(): if products > 1: products -= 1 print("Consumer(%s):consume one, now products:%s" %(self.name, products)) condition.notify() condition.release() else: print("Consumer(%s):only 1, stop consume, products:%s" %(self.name, products)) condition.wait() time.sleep(2) if __name__ == "__main__": for p in range(0, 2): p = Producer() p.start() for c in range(0, 3): c = Consumer() c.start()
condition.notifyAll()
import threading alist = None condition = threading.Condition() def doSet(): if condition.acquire(): while alist is None: condition.wait() for i in range(len(alist))[::-1]: alist[i] = 1 print(alist[i]) condition.notify() condition.release() def doPrint(): if condition.acquire(): while alist is None: condition.wait() for i in alist: print(i) print() condition.notify() condition.release() def doCreate(): global alist if condition.acquire(): if alist is None: alist = [0 for i in range(10)] condition.notifyAll() condition.release() tset = threading.Thread(target=doSet, name='tset') tprint = threading.Thread(target=doPrint, name='tprint') tcreate = threading.Thread(target=doCreate, name='tcreate') tset.start() tprint.start() tcreate.start()
-