併發編程是咱們編程中經常使用的優化程序性能的手段,能提升CPU的使用率。通常使用是多線程,多進程,協程python
咱們目前跑的python程序大多數都是在cpython上運行的。cpython是有一個全局解釋鎖,具體什麼意思,能夠兩個方面理解編程
也就是說python多線程是有必定侷限性的,對於io密集型的任務,咱們能夠很好的利用多線程,對於計算密集型的任務,多線程就沒有意義了,只能利用多進程提升性能安全
第一種形式:使用threading庫,直接使用Thread類多線程
from threading import Thread import time def fun(n): # do something time.sleep(n) print(n) t1 = Thread(target=fun, args=(1,)) t2 = Thread(target=fun, args=(2,)) # 啓動線程 t1.start() t2.start() # 等待線程結束 t1.join() t2.join()
第二種形式:使用threading庫,繼承Thread類併發
from threading import Thread import time class MyThread(Thread): def __init__(self, n): self.n = n super().__init__() def run(self): time.sleep(self.n) print(self.n) t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start() t1.join() t2.join()
多個線程在同一個進程中運行時,是共享內存空間的,這就有可能引發一個線程在修改一個變量到一半的時候去休息了,回來的時候發現這個變量變了,這就會產生問題app
如何保證線程安全,通常有下面幾個方法性能
還有一些其餘方法保證線程安全,不一樣的場景,保證線程安全的方法也不一樣,須要合理採用。下面用代碼說明一下優化
首先,咱們不加鎖,有兩個線程共同操做一個共享變量ui
# 沒有使用鎖的狀況 from threading import Thread n = 0 def add(): global n for i in range(1000000): n += 1 def sub(): global n for i in range(1000000): n -= 1 t1 = Thread(target=add) t2 = Thread(target=sub) t1.start() t2.start() t1.join() t2.join() print(n)
輸出的結果,發現不是咱們想象中的0,這就是由於兩個線程運行期間,一個線程把 對n的操做執行到一半時候,去休息了,回來繼續執行剩下一半的過程當中,另外一個線程修改了n,這樣就形成了線程不安全的問題,最後的結果不一致。spa
加鎖後的代碼
from threading import Thread, Lock n = 0 lock = Lock() def add(lock): global n for i in range(1000000): lock.acquire() n += 1 lock.release() def sub(lock): global n for i in range(1000000): lock.acquire() n -= 1 lock.release() t1 = Thread(target=add, args=(lock,)) t2 = Thread(target=sub, args=(lock,)) t1.start() t2.start() t1.join() t2.join() print(n)
此次輸出的結果都是0了,咱們在整個修改共享變量的過程當中,加了鎖,在鎖未釋放的時候,其餘線程是運行不了的
上面咱們知道存在線程安全的問題,因此通訊的過程當中,咱們要考慮線程安全的問題
線程間通訊最經常使用的是隊列,python提供了一個線程安全的隊列,from queue import Queue,在多個線程之間操做隊列裏的元素都是安全的
還有更加複雜的線程通訊機制,好比條件變量,信號量等,python也提供了相應的模塊
有時候,咱們有不少條數據,咱們但願同一時刻總共有5個線程來處理這一批數據,一個線程結束後,再啓動另外一個線程,總數不能超過5個,這時候線程池就能夠很好的解決咱們的問題了
from concurrent.futures.thread import ThreadPoolExecutor from concurrent.futures import wait import time pools = ThreadPoolExecutor(5) data = list(range(20)) res = [] def fun(n): time.sleep(2) print(n) return n for n in data: res.append(pools.submit(fun, n))
# 等待全部線程執行完畢 wait(res)
線程池還給咱們提供了更多的功能,咱們能夠獲取線程返回的結果
from concurrent.futures.thread import ThreadPoolExecutor from concurrent.futures import as_completed import time pools = ThreadPoolExecutor(5) data = list(range(20)) res = [] def fun(n): time.sleep(2) return n for n in data: res.append(pools.submit(fun, n)) # 獲取線程結束後的結果 for r in as_completed(res): print(r.result())
關於python多進程,在大多數業務開發中,用的比較少,我只給出一個簡單的例子(實際上進程在運行的時候比這個負責,是會拷貝一份父進程的信息的)
from multiprocessing import Process import time def fun(n): # do something time.sleep(n) print(n) if __name__ == "__main__": p1 = Process(target=fun, args=(1,)) p2 = Process(target=fun, args=(2,)) p1.start() p2.start() p1.join() p2.join()
併發編程是咱們提升程序性能的經常使用手段,通常來講就是多線程,多進程,協程,併發編程的時候,咱們還有許多問題須要考慮,線程安全,通訊等等。