進程有不少優勢,它提供了多道編程,讓咱們感受咱們每一個人都擁有本身的CPU和其餘資源,能夠提升計算機的利用率。不少人就不理解了,既然進程這麼優秀,爲何還要線程呢?其實,仔細觀察就會發現進程仍是有不少缺陷的,主要體如今兩點上:python
進程只能在一個時間幹一件事,若是想同時幹兩件事或多件事,進程就無能爲力了。編程
進程在執行的過程當中若是阻塞,例如等待輸入,整個進程就會掛起,即便進程中有些工做不依賴於輸入的數據,也將沒法執行。json
線程,有時被稱爲輕量進程(Lightweight Process,LWP),是程序執行流的最小單元,也是操做系統可以進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運做單位。一條線程指的是進程中一個單一順序的控制流,一個進程中能夠併發多個線程,每條線程並行執行不一樣的任務。緩存
線程與進程的區別:多線程
首先須要明確的一點是GIL並非Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。就比如C++是一套語言(語法)標準,可是能夠用不一樣的編譯器來編譯成可執行代碼。有名的編譯器例如GCC,INTEL C++,Visual C++等。Python也同樣,一樣一段代碼能夠經過CPython,PyPy,Psyco等不一樣的Python執行環境來執行。像其中的JPython就沒有GIL。然而由於CPython是大部分環境下默認的Python執行環境。因此在不少人的概念裏CPython就是Python,也就想固然的把GIL歸結爲Python語言的缺陷。因此這裏要先明確一點:GIL並非Python的特性,Python徹底能夠不依賴於GIL。併發
那麼什麼是GIL呢,下面是官方文檔給出的解釋:app
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)dom
爲何會有GIL的緣由:ide
一、直接調用多線程 性能
import threading import time def run(n): time.sleep(3) print('task',n) t1 = threading.Thread(target=run,args=('t1',)) t2 = threading.Thread(target=run,args=('t2',)) t1.start() t2.start()
t1.join() #等待線程t1執行完畢
PS:
#threading.current_thread() 打印當前線程
#threading.active_count() 打印當前活躍線程數
二、繼承式調用多線程(通常這種用的比較少)
import threading import time class MyThread(threading.Thread): def __init__(self, num): threading.Thread.__init__(self) self.num = num def run(self): time.sleep(3) print("running on number:%s" % self.num) if __name__ == '__main__': t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start()
在線程start以前,能夠把線程變成守護線程,守護線程服務於非守護線程,當非守護線程執行完畢以後,程序直接結束,不會管非守護線程是否執行完。t.setDaemon(True),t爲生成的線程。
python在使用多線程的時候已經有了GIL爲何還須要線程鎖呢?由於python 的GIL只能控制同一時間只能有一個線程在運行,可是不能控制同一時間只有一個線程在修改數據。有人會問,既然都已經只有一個線程在運行,爲何還會有多個線程在修改數據呢?咱們看下面的圖。。。
import time import threading def addNum(): global num #在每一個線程中都獲取這個全局變量 print('--get num:',num ) time.sleep(1) num -=1 #對此公共變量進行-1操做 num = 100 #設定一個共享變量 thread_list = [] for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: #等待全部線程執行完畢 t.join() print('final num:', num )
PS:不要在3.x上運行,3.x上的結果老是正確的,多是內部加了鎖
正常來說,這個num結果應該是0, 但在Linux的python 2.7上多運行幾回,會發現,最後打印出來的num結果不老是0,爲何每次運行的結果不同呢? 哈,很簡單,假設你有A,B兩個線程,此時都 要對num 進行減1操做, 因爲2個線程是併發同時運行的,因此2個線程頗有可能同時拿走了num=100這個初始變量交給cpu去運算,當A線程去處完的結果是99,但此時B線程運算完的結果也是99,兩個線程同時CPU運算的結果再賦值給num變量後,結果就都是99。那怎麼辦呢? 很簡單,每一個線程在要修改公共數據時,爲了不本身在還沒改完的時候別人也來修改此數據,能夠給這個數據加一把鎖, 這樣其它線程想修改此數據時就必須等待你修改完畢並把鎖釋放掉後才能再訪問此數據。
import time import threading def addNum(): global num # 在每一個線程中都獲取這個全局變量 lock.acquire() # 修改數據前加鎖 num -= 1 # 對此公共變量進行-1操做 lock.release() # 修改後釋放 num = 100 # 設定一個共享變量 thread_list = [] lock = threading.Lock() # 生成全局鎖 for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: # 等待全部線程執行完畢 t.join() print('final num:', num)
PS:就算之後用3.x的版本也要本身加鎖,即便不加鎖結果正確也要加,由於官方文檔沒有明確指出3.x的版本內部加了鎖。
說白了就是鎖中還有鎖的時候,就不能簡單地使用互斥鎖,要使用遞歸鎖。
import threading 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)
互斥鎖同時只容許一個線程更改數據,而Semaphore是同時容許必定數量的線程更改數據 。
import threading, time def run(n): semaphore.acquire() time.sleep(1) print("run the thread: %s\n" % n) semaphore.release() num = 0 semaphore = threading.BoundedSemaphore(5) # 最多容許5個線程同時運行 for i in range(20): t = threading.Thread(target=run, args=(i,)) t.start() while threading.active_count() != 1: pass else: print('----all threads done---') print(num)
事件處理的機制:全局定義了一個「Flag」,若是「Flag」值爲 False,那麼當程序執行 event.wait 方法時就會阻塞;若是「Flag」值爲True,那麼執行event.wait 方法時便再也不阻塞。
用 threading.Event 實現線程間通訊,使用threading.Event可使一個線程等待其餘線程的通知,咱們把這個Event傳遞到線程對象中,
Event默認內置了一個標誌,初始值爲False。一旦該線程經過wait()方法進入等待狀態,直到另外一個線程調用該Event的set()方法將內置標誌設置爲True時,該Event會通知全部等待狀態的線程恢復運行。
經過Event來實現兩個或多個線程間的交互,下面是一個紅綠燈的例子,即起動一個線程作交通指揮燈,生成幾個線程作車輛,車輛行駛按紅燈停,綠燈行的規則。
import threading, time import random def light(): if not event.isSet(): #初始化evet的flag爲真 event.set() #wait就不阻塞 #綠燈狀態 count = 0 while True: if count < 10: print('\033[42;1m---green light on---\033[0m') elif count < 13: print('\033[43;1m---yellow light on---\033[0m') elif count < 20: if event.isSet(): event.clear() print('\033[41;1m---red light on---\033[0m') else: count = 0 event.set() #打開綠燈 time.sleep(1) count += 1 def car(n): while 1: time.sleep(random.randrange(3, 10)) #print(event.isSet()) if event.isSet(): print("car [%s] is running..." % n) else: print('car [%s] is waiting for the red light...' % n) event.wait() #紅燈狀態下調用wait方法阻塞,汽車等待狀態 if __name__ == '__main__': car_list = ['BMW', 'AUDI', 'SANTANA'] event = threading.Event() Light = threading.Thread(target=light) Light.start() for i in car_list: t = threading.Thread(target=car, args=(i,)) t.start()
queue.
Queue
(maxsize=0) #先入先出queue.
LifoQueue
(maxsize=0) #last in fisrt out queue.
PriorityQueue
(maxsize=0) #存儲數據時可設置優先級的隊列隊列經常使用方法
Queue.
qsize
()
Queue.
empty
() #return True if empty
Queue.
full
() # return True if full
Queue.
put
(item, block=True, timeout=None) # priority_number, data
Queue.put
_nowait
()
Queue.
get
()
Queue.
get_nowait
()
import queue q2 = queue.Queue() q1 = queue.LifoQueue() q = queue.PriorityQueue() q.put((8,'alex')) q.put((6,'wusir')) q.put((7,'json')) print(q.get()) print(q.get()) print(q.get()) print(q.get(timeout=3)) #自由修改數據本身測試