python併發編程

併發編程是咱們編程中經常使用的優化程序性能的手段,能提升CPU的使用率。通常使用是多線程,多進程,協程python

 1、python的全局解釋鎖GIL

咱們目前跑的python程序大多數都是在cpython上運行的。cpython是有一個全局解釋鎖,具體什麼意思,能夠兩個方面理解編程

  1. 在同一時刻,只能運行一個線程
  2. 沒法將多個線程映射到多個cpu上運行

也就是說python多線程是有必定侷限性的,對於io密集型的任務,咱們能夠很好的利用多線程,對於計算密集型的任務,多線程就沒有意義了,只能利用多進程提升性能安全

2、簡單多線程編程

第一種形式:使用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()

 

3、線程安全

多個線程在同一個進程中運行時,是共享內存空間的,這就有可能引發一個線程在修改一個變量到一半的時候去休息了,回來的時候發現這個變量變了,這就會產生問題app

如何保證線程安全,通常有下面幾個方法性能

  1. 使用私有變量,每一個線程都只知道本身的變量,其餘線程沒法修改,通常經過局部變量實現
  2. 經過拷貝數據到本身的空間,本身修改本身拷貝的數據,這樣不會影響其餘線程,通常經過thread.local實現
  3. 控制共享變量的訪問方式,同一時刻只能有一個線程修改,通常經過加鎖實現

還有一些其餘方法保證線程安全,不一樣的場景,保證線程安全的方法也不一樣,須要合理採用。下面用代碼說明一下優化

首先,咱們不加鎖,有兩個線程共同操做一個共享變量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了,咱們在整個修改共享變量的過程當中,加了鎖,在鎖未釋放的時候,其餘線程是運行不了的

4、線程間通訊

上面咱們知道存在線程安全的問題,因此通訊的過程當中,咱們要考慮線程安全的問題

線程間通訊最經常使用的是隊列,python提供了一個線程安全的隊列,from queue import Queue,在多個線程之間操做隊列裏的元素都是安全的

還有更加複雜的線程通訊機制,好比條件變量,信號量等,python也提供了相應的模塊

5、線程池

有時候,咱們有不少條數據,咱們但願同一時刻總共有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())

 

6、多進程編程

關於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()

 

7、總結

併發編程是咱們提升程序性能的經常使用手段,通常來講就是多線程,多進程,協程,併發編程的時候,咱們還有許多問題須要考慮,線程安全,通訊等等。

相關文章
相關標籤/搜索