進程、線程
http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
使用threading模塊實現多線程編程[綜述]
Python這門解釋性語言也有專門的線程模型,Python虛擬機使用GIL(Global Interpreter Lock,全局解釋器鎖)來互斥線程對共享資源的訪問,但暫時無法利用多處理器的優勢。
在Python中我們主要是通過thread和 threading這兩個模塊來實現的,其中Python的threading模塊是對thread做了一些包裝的,可以更加方便的被使用,所以我們使用 threading模塊實現多線程編程。這篇文章我們主要來看看Python對多線程編程的支持。
在語言層面,Python對多線程提供了很好的支持,可以方便地支持創建線程、互斥鎖、信號量、同步等特性。下面就是官網上介紹threading模塊的基本資料及功能:
實現模塊
thread:多線程的底層支持模塊,一般不建議使用;
threading:對thread進行了封裝,將一些線程的操作對象化。
threading模塊
Thread 線程類,這是我們用的最多的一個類,你可以指定線程函數執行或者繼承自它都可以實現子線程功能;
Timer與Thread類似,但要等待一段時間後纔開始運行;
Lock 鎖原語,這個我們可以對全局變量互斥時使用;
RLock 可重入鎖,使單線程可以再次獲得已經獲得的鎖;
Condition 條件變量,能讓一個線程停下來,等待其他線程滿足某個“條件”;
Event 通用的條件變量。多個線程可以等待某個事件發生,在事件發生後,所有的線程都被激活;
Semaphore爲等待鎖的線程提供一個類似“等候室”的結構;
BoundedSemaphore 與semaphore類似,但不允許超過初始值;
Queue:實現了多生產者(Producer)、多消費者(Consumer)的隊列,支持鎖原語,能夠在多個線程之間提供很好的同步支持。
其中Thread類
是你主要的線程類,可以創建進程實例。該類提供的函數包括:
getName(self) 返回線程的名字
isAlive(self) 布爾標誌,表示這個線程是否還在運行中
isDaemon(self) 返回線程的daemon標誌
join(self, timeout=None) 程序掛起,直到線程結束,如果給出timeout,則最多阻塞timeout秒
run(self) 定義線程的功能函數
setDaemon(self, daemonic) 把線程的daemon標誌設爲daemonic
setName(self, name) 設置線程的名字
start(self) 開始線程執行
其中Queue提供的類
Queue隊列
LifoQueue後入先出(LIFO)隊列
PriorityQueue 優先隊列
Python threading模塊
#!/usr/bin/env python #-*- coding:utf-8 -*- # Author:DCC import threading import time def run(n): print("task",n) time.sleep(2) t1 = threading.Thread(target=run,args=("t1",)) t2 = threading.Thread(target=run,args=("t2",)) t1.start() t2.start() print(t1.getName()) print(t2.getName())
#!/usr/bin/env python #-*- coding:utf-8 -*- # Author:DCC import threading import time class MyThread(threading.Thread): def __init__(self,n): super(MyThread,self).__init__() #注意:一定要顯式的調用父類的初始化函數。 self.n = n def run(self): #重寫父類run方法,在線程啓動後執行該方法內的代碼。 print("running task",self.n) time.sleep(3) t1 = MyThread("t1") t2 = MyThread("t2") if __name__ == '__main__': t1.start() t2.start() print(t1.getName()) print(t2.getName())
Join & Daemon
一般情況下 主線程是不等待子線程是否執行完成的,只是觸發一下,就不管了。
1、join ()方法:主線程A中,創建了子線程B,並且在主線程A中調用了B.join(),那麼,主線程A會在調用的地方等待,直到子線程B完成操作後,纔可以接着往下執行,那麼在調用這個線程時可以使用被調用線程的join方法。
原型:join([timeout])
裏面的參數時可選的,代表線程運行的最大時間,即如果超過這個時間,不管這個此線程有沒有執行完畢都會被回收,然後主線程或函數都會接着執行的。
import threading import time class MyThread(threading.Thread): def __init__(self,id): threading.Thread.__init__(self) self.id = id def run(self): x = 0 time.sleep(10) print self.id if __name__ == "__main__": t1=MyThread(999) t1.start() for i in range(5): print i #執行結果 0 1 2 3 4 999
機器上運行時,4和999之間,有明顯的停頓。解釋:線程t1 start後,主線程並沒有等線程t1運行結束後再執行,而是先把5次循環打印執行完畢(打印到4),然後sleep(10)後,線程t1把傳進去的999纔打印出來。
現在,我們把join()方法加進去(其他代碼不變),看看有什麼不一樣,例子:
import threading import time class MyThread(threading.Thread): def __init__(self,id): threading.Thread.__init__(self) self.id = id def run(self): x = 0 time.sleep(10) print self.id if __name__ == "__main__": t1=MyThread(999) t1.start() t1.join() for i in range(5): print i #執行結果 999 0 1 2 3 4
2、setDaemon()方法。主線程A中,創建了子線程B,並且在主線程A中調用了B.setDaemon(),這個的意思是,把主線程A設置爲守護線程,這時候,要是主線程A執行結束了,就不管子線程B是否完成,一併和主線程A退出.這就是setDaemon方法的含義,這基本和join是相反的。此外,還有個要特別注意的:必須在start() 方法調用之前設置,如果不設置爲守護線程,程序會被無限掛起。
例子:就是設置子線程隨主線程的結束而結束:
#!/usr/bin/env python #-*- coding:utf-8 -*- # Author:DCC import threading import time def run(n): print("task",n) time.sleep(2) print("thread done...",n) start_time = time.time() t_objs = [] #存線程實例 for i in range(50): t = threading.Thread(target=run,args=("t-%s"% i,)) t.setDaemon(True) #把當前線程設置爲守護線程 t.start() t_objs.append(t) # 爲了不阻塞後面線程的啓動,不在這裏join,先放到-個列表裏 # print(t.getName()) #time.sleep(2) print(threading.active_count()) # for i in t_objs: #循環線程實例列表,等待所有線程執行完畢 # t.join() print(time.time()-start_time)
線程鎖
CPU執行任務時,在線程之間是進行隨機調度的,並且每個線程可能只執行n條代碼後就轉而執行另外一條線程。由於在一個進程中的多個線程之間是共享資源和數據的,這就容易造成資源搶奪或髒數據,於是就有了鎖的概念,限制某一時刻只有一個線程能訪問某個指定的數據。
未枷鎖
import threading import time NUM = 0 def show(): global NUM NUM += 1 name = t.getName() time.sleep(1) # 注意,這行語句的位置很重要,必須在NUM被修改後,否則觀察不到髒數據的現象。 print(name, "執行完畢後,NUM的值爲: ", NUM) for i in range(10): t = threading.Thread(target=show) t.start() print('main thread stop')
LOCK鎖
普通鎖,也叫互斥鎖,是獨佔的,同一時刻只有一個線程被放行。
import time import threading NUM = 10 def func(lock): global NUM lock.acquire() # 讓鎖開始起作用 NUM -= 1 time.sleep(1) print(NUM) lock.release() # 釋放鎖 lock = threading.Lock() # 實例化一個鎖對象 for i in range(10): t = threading.Thread(target=func, args=(lock,)) # 記得把鎖當作參數傳遞給func參數 t.start()
RLock(遞歸鎖)
說白了就是在一個大鎖中還要再包含子鎖
threading模塊的Lock類,它不支持嵌套鎖。RLcok類的用法和Lock一模一樣,但它支持嵌套,因此我們一般直接使用RLcok類。
import threading, time def run1(): print("grab the first part data") lock.acquire() global num num += 1 lock.release() return num def run2(): print("grab the second part data") lock.acquire() global num2 num2 += 1 lock.release() return num2 def run3(): lock.acquire() res = run1() print('--------between run1 and run2-----') res2 = run2() lock.release() print(res, res2) if __name__ == '__main__': num, num2 = 0, 0 lock = threading.RLock() for i in range(10): t = threading.Thread(target=run3) t.start() while threading.active_count() != 1: print(threading.active_count()) else: print('----all threads done---') print(num, num2)
時器(Timer)
定時器,指定n秒後執行某操作。很簡單但很使用的東西。
from threading import Timer def hello(): print("hello, world") t = Timer(1, hello) # 表示1秒後執行hello函數 t.start()
信號量(Semaphore)
這種鎖允許一定數量的線程同時更改數據,它不是互斥鎖。比如地鐵安檢,排隊人很多,工作人員只允許一定數量的人進入安檢區,其它的人繼續排隊。
互斥鎖 同時只允許一個線程更改數據,而Semaphore是同時允許一定數量的線程更改數據 ,比如廁所有3個坑,那最多隻允許3個人上廁所,後面的人只能等裏面有人出來了才能再進去。
import time import threading def run(n): semaphore.acquire() print("run the thread: %s" % n) time.sleep(1) semaphore.release() num = 0 semaphore = threading.BoundedSemaphore(5) # 最多允許5個線程同時運行 for i in range(20): t = threading.Thread(target=run, args=(i,)) t.start()
事件(Event)
事件主要提供了三個方法 set、wait、clear。
事件機制:全局定義了一個“Flag”,如果“Flag”的值爲False,那麼當程序執行wait方法時就會阻塞,如果“Flag”值爲True,那麼wait方法時便不再阻塞。這種鎖,類似交通紅綠燈(默認是紅燈),它屬於在紅燈的時候一次性阻擋所有線程,在綠燈的時候,一次性放行所有的排隊中的線程。
clear:將“Flag”設置爲False,當程序執行wait方法時就會阻塞所有線程。
set:將“Flag”設置爲True,wait方法時便不再阻塞
!/usr/bin/env python #-*- coding:utf-8 -*- # Author:DCC import time import threading import random event = threading.Event() def lighter(): count = 0 event.set() #先設置成綠燈 while True: if count > 5 and count < 10: #改成紅燈 event.clear() #把標誌位清除 print("\033[41;1m red.....\033[0m") elif count > 10: event.set() #設置成路燈 count = 0 else: print("\033[42;1m green \033[0m") time.sleep(1) count +=1 def car(name): while True: if event.is_set(): #代表綠燈 print("[%s] is running " % name) time.sleep(2) else: print("[%s] is waitting....... " % name) event.wait() print("[%s] green light is on ,start going" % name) light = threading.Thread(target=lighter,) light.start() car1 = threading.Thread(target=car,args=("Tesla",)) car1.start()
隊列
通常而言,隊列是一種先進先出的數據結構,與之對應的是堆棧這種後進先出的結構。但是在python中,它內置了一個queue模塊,它不但提供普通的隊列,還提供一些特殊的隊列
Queue:先進先出隊列
import queue q = queue.Queue(5) q.put(11) q.put(22) q.put(33) print(q.get()) print(q.get()) print(q.get())
Queue類的參數和方法:
qsize() 獲取當前隊列中元素的個數,也就是隊列的大小
empty() 判斷當前隊列是否爲空,返回True或者False
full() 判斷當前隊列是否已滿,返回True或者False
put(self, block=True, timeout=None)
get(self, block=True, timeout=None)
LifoQueue:後進先出隊列
import queue q = queue.LifoQueue() q.put(123) q.put(456) print(q.get())
PriorityQueue:優先級隊列
q = queue.PriorityQueue() q.put((1,"alex1")) q.put((1,"alex2")) q.put((1,"alex3")) q.put((3,"alex3")) print(q.get())
生產者消費者模型
import time,random import queue,threading q = queue.Queue() def Producer(name): count = 0 while count <20: time.sleep(random.randrange(3)) q.put(count) print('Producer %s has produced %s baozi..' %(name, count)) count +=1 def Consumer(name): count = 0 while count <20: time.sleep(random.randrange(4)) if not q.empty(): data = q.get() print(data) print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data)) else: print("-----no baozi anymore----") count +=1 p1 = threading.Thread(target=Producer, args=('A',)) c1 = threading.Thread(target=Consumer, args=('B',)) p1.start() c1.start()