一、python的多線程python
多線程就是在同一時刻執行多個不一樣的程序,然而python中的多線程並不能真正的實現並行,這是因爲cpython解釋器中的GIL(全局解釋器鎖)搗的鬼,這把鎖保證了同一時刻只有一個線程被執行。linux
多線程的特色:網絡
線程比進程更輕量級,建立一個線程要比建立一個進程快10-100倍。多線程
線程共享全局變量。併發
因爲GIL的緣由,當一個線程遇到IO操做時,會切換到另外一個線程,因此線程適合IO密集型操做。app
在多核cpu系統中,最大限度的利用多核,能夠開啓多個線程,開銷比進程小的多,可是這並不適合python。dom
多線程互斥鎖:socket
由於線程共享全局變量,因此須要互斥鎖去限制線程對全局變量的更改。async
假設,當一個線程在執行到獲取全局變量的時候,這個後GIL切換到另外一個線程執行,這個時候新的線程爲全局變量+1後切換回以前的線程,以前的線程中的全局變量仍是+1前的值,因此須要互斥鎖。函數
爲何有了GIL鎖還要互斥鎖呢?
GIL鎖只是控制同一時刻下只有一個線程被執行,這並不能控制同一時刻只有一個線程去獲取並更改全局變量,因此須要使用互斥鎖。
多線程的實現:
# 導入threading模塊 import threading # 定義全局變量 i=0 # 定義互斥鎖 mutex = threading.Lock() def a(): # 申明全局變量i global i for j in range(2000000): # 獲取互斥鎖 mutex.acquire() i+=1 # 釋放互斥鎖 mutex.release() def b(): global i for j in range(2000000): mutex.acquire() i+=1 mutex.release() # 建立線程 t1 = threading.Thread(target=a) t2 = threading.Thread(target=b) # 開啓線程 t1.start() t2.start() # 等待全部線程結束 t1.join() t2.join() print(i)
二、python中的多進程
python的多線程不能利用多核的優點,若是想要充分的利用多核cpu的資源,python中大部分狀況須要使用多進程。
python多進程的特色:
進程間不共享全局變量,進程修改的數據僅限於該進程內。
進程建立和銷燬的開銷比較大。
相對於線程,進程更適合與計算密集型操做。
能充分利用多核的優點。
進程間通訊:
既然進程間中不公共享全局變量,那麼多進程間怎麼進行通訊呢?能夠使用multiprocessing中的Queue模塊,固然也能夠使用socket、管道、共享內存等方式。
多進程的實現:
# 導入multiprocessin模塊 import multiprocessing # 建立隊列 queue = multiprocessing.Queue() # 定義全局變量 a = 0 # 定義函數 def work1(num): # 獲取隊列中的數據,若是沒有數據,將堵塞 a = queue.get() # 將隊列中的數據+2000000次num for i in range(2000000): a+=num # 將數據存放在隊列中 queue.put(a) # 打印最終結果 print("work1",a) # 定義函數 def work2(): # 申明全局變量a global a # 將a+2000000次1 for i in range(2000000): a+=1 # 打印最總結果 print("work2",a) # 將a存放在隊列中 queue.put(a) # 建立進程 p1 = multiprocessing.Process(target=work1, args=(2,)) p2 = multiprocessing.Process(target=work2) # 啓動進程 p1.start() p2.start() # 等待進程結束 p1.join() p2.join() # 獲取隊列中的數據 a = queue.get() # 打印a print(a)
進程池的實現
進程池能減小重複建立和銷燬進程的開銷問題
# 導入須要的模塊 import multiprocessing import time import random # 定義函數 def work(num): print("num=",num) time.sleep(random.randint(0,2)) # 建立進程池,設置進程的數量 pool = multiprocessing.Pool(3) for i in range(10): # 開啓進程 pool.apply_async(work, args=(i,)) # 設置等待時間,等待全部進程結束 time.sleep(20)
三、python中的協程
在linux中線程就是輕量級的進程,而咱們一般也把協程稱爲輕量級的線程。
對比進程和協程:
進程是內核調度,而協程是在用戶態調度,因此說進程的上下文在內核態保存恢復,而協程是在用戶態保存恢復的,因此協程的開銷比進程低。
進程會被搶佔,而協程不會,也就是說協程若是不主動讓出cpu,那麼其餘的協程就沒有執行的機會。
進程所須要的內存比協程大得多
對比線程和協程:
線程的上下文切換成本相對於協程來講比較高。
線程的切換由操做系統來控制,而協程的切換由咱們本身控制。
yield實現協程:
# 定義兩個函數 def work1(): while True: print("work1") # 當程序運行到yield就會暫停,等待下次的next調用,而後繼續執行 yield def work2(): while True: print("work2") yield w1 = work1() w2 = work2() while True: # 使用next函數啓動 next(w1) next(w2)
greenlet實現協程:
greenlet安裝:
sudo pip3 install greenlet
code:
# 導入greenlet模塊 from greenlet import greenlet def work1(): for i in range(10): print("work1") # 打印事後跳轉至協程g2繼續執行 g2.switch() def work2(): for i in range(10): print("work2") # 打印後跳轉至協程g1繼續執行 g1.switch() # 建立協程g1 g1 = greenlet(work1) # 建立協程g2 g2 = greenlet(work2) # 跳轉至協程g1 g1.switch()
gevent實現協程:
gevent是基於greenlet的併發網絡庫,每當有一個協程堵塞的時,程序將自動調度。
monkey-patching:
通常稱爲猴子補丁,這個補丁能直接修改標準庫裏面大部分的阻塞式系統調用。可是若是在複雜的生產環境中使用了這些標準庫,可能就會由於打了補丁而出現奇怪的問題。
gevent安裝:
sudo pip3 install gevent
code:
# 導入所須要的模塊 import gevent import time from gevent import monkey # 猴子補丁,monkey.patch_all()方法將全部的標準庫都替換掉 # 使用猴子補丁褒貶不一,可是官網上仍是建議使用patch_all(),並且在程序的第一行就執行 monkey.patch_all() def f(n): for i in range(n): print(i) # 設置延時 time.sleep(0.5) # 若是沒有導入monkey模塊的話,須要使用gevent.sleep() # gevent.sleep(0.5) # ----------------寫法一-------------------- # 建立greenlet協程對象 # g1 = gevent.spawn(f,5) # g2 = gevent.spawn(f,5) # g3 = gevent.spawn(f,5) # 等待全部greenlet攜程結束後退出 # g1.join() # g2.join() # g3.join() # ----------------寫法二-------------------- gevent.joinall([gevent.spawn(f,5), gevent.spawn(f,5), gevent.spawn(f,5)])