1. 程式人生 > >python協程--asyncio模組(基礎併發測試)

python協程--asyncio模組(基礎併發測試)

在高併發的場景下,python提供了一個多執行緒的模組threading,但似乎這個模組並不近人如意,原因在於cpython本身的全域性解析鎖(GIL)問題,在一段時間片內實際上的執行是單執行緒的。同時還存在著資源爭奪的問題。python3.4之後引入了基於生成器物件的協程概念。也就是asyncio模組。除了asyncio模組,python在高併發這一問題還提出了另外一些解決方案,例如tornado和gevent都實現了類似的功能。由此,在方案選擇上提供了更多的可能性。以下是threading模組和asyncio模組對比測試實驗。asyncio模組的具體使用,我希望自己在另一篇文章再寫。

 

一、threading模組

threading模組中的thread執行緒 密集 運算爭奪變數測試

程式碼:

#多執行緒共有資料的爭奪檢測
from threading import Thread,currentThread
import time
def do_something(x):
    global a
    time.sleep(x)
    for b in range(1,51):   #計算從1+...+50
        a+=b
    print(currentThread(),":",a)

a = 0
threads = []

for i  in  range(1,20000):                        #
為了突出效果,執行緒量開到接近20000 thread = Thread(target=do_something,args=(1,)) threads.append(thread) for thread in threads: thread.start()

 

擷取部分結果:
<Thread(Thread-19972, started 34476)> : 25408200
<Thread(Thread-19971, started 34548)> : 25409475
<Thread(Thread-19991, started 12644)> : 25410750
<Thread(Thread-19990, started 34580)> : 25412025
<Thread(Thread-19989, started 34404)> : 25413300
<Thread(Thread-19986, started 34044)> : 25414575
<Thread(Thread-19983, started 34648)> : 25415850
<Thread(Thread-19982, started 34128)> : 25417125

執行時間:
6.9629926681518555 6.8796374797821045 7.3379065990448 平均執行時間:7.0秒

 

由結果可以看出,多執行緒在密集型運算的(佔用大量CPU運算單元)情況下,會出現前後同一變數的資料不一致的情況。也就是所謂的“競態問題”。

 

 

二、asyncio模組

asyncio模組 密集運算測試(執行緒安全!不存在爭奪資源問題),所以協程在密集運算和IO併發上都有很強的支援。

程式碼:

#密集運算測試
import asyncio

a = 0
tasks = []
num = 0
async def do_something(x):
    global a
    global num
    #num += 1         # 思路3:num自增的位置(在阻塞前/後)不同會產生不同的結果
    await asyncio.sleep(x)
    for b in range(1,51):   #計算從1+...+50
        a+=b
    num += 1            #思路1
print("this is coroutetime",":",num,a)    #思路1,思路3
print("this is coroutetime",":",x,a)       #思路2

for i in range(1,20000):             #即使睡眠的時間很短,運算量大都不會產生資源爭奪
coroutine = do_something(1)                   #思路1
# coroutine = do_something(i*0.01)              #思路2
# coroutine = do_something(3//i)                #思路3
    tasks.append(asyncio.ensure_future(coroutine))

loop = asyncio.get_event_loop()      #建立事件迴圈
loop.run_until_complete(asyncio.wait(tasks))     #將協程塞進事件迴圈中

 

程式碼實現思路:

(1)思路1:每一個協程的睡眠時間相同,也就是說幾乎是同時在執行密集型計算,並用num自增計算作為協程的代號。

(2)思路2:為了區別開不同協程的佔據CPU的執行時間片,我對睡眠時間進行了一個乘法運算,協程代號越大的協程睡眠時間越長,並用時間作為協程代號的記錄。

(3)思路3:這次我將睡眠時間作一個調整,用除法運算,也就是說,協程代號越大的,睡眠時間越短,不過這次協程代號用num來記錄,並且放在了睡眠(阻塞)之前。

 

摘取前幾個資料

思路1:當設定的睡眠時間(阻塞時間)相同時,結果的列印幾乎是同時出現
this is coroutetime : 1 1275
this is coroutetime : 2 2550
this is coroutetime : 3 3825
this is coroutetime : 4 5100
this is coroutetime : 5 6375
this is coroutetime : 6 7650
this is coroutetime : 7 8925
this is coroutetime : 8 10200

思路1執行時間: 3.0337979793548584 3.159485340118408 3.095968008041382 平均執行時間3.08秒

 

思路2:當設定的睡眠時間(阻塞時間)不同,協程代號就是睡眠的時間
this is coroutetime : 0.01 1275
this is coroutetime : 0.02 2550
this is coroutetime : 0.03 3825
this is coroutetime : 0.04 5100
this is coroutetime : 0.05 6375
this is coroutetime : 0.06 7650
this is coroutetime : 0.07 8925
this is coroutetime : 0.08 10200

 

由上面兩組資料可以看出,無論協程是同時進行還是分時間段進行,都是嚴格按照順序來執行的。思路2的結果很符合我們的認知常識,那麼思路1的結果是怎麼得來的呢?原因在於,多併發(此處的密集型運算用於模擬一系列的併發內部操作)情況下,阻塞的協程會暫時被擱置,切換到另外的協程。可以將協程的執行理解為一個佇列,當大量協程來臨的時候,無法一次性執行,於是放進一個類似佇列的容器(WeakSet),並且不斷檢測這個佇列中哪一個協程是處於非阻塞狀態的,去呼叫這個協程的資源並執行。佇列中的每一個元素間是互不干擾的。於是,就出現了以上的結果----有序的協程執行。

 

思路3:再看下面一組資料
this is coroutetime : 1999 1275
this is coroutetime : 1999 2550
this is coroutetime : 1999 3825
this is coroutetime : 1999 5100
this is coroutetime : 1999 6375
this is coroutetime : 1999 7650
this is coroutetime : 1999 8925
this is coroutetime : 1999 10200

 

為什麼所有的協程號都一樣

因為最大協程號,睡眠時間最短,所以它先執行輸出,而協程號是累加的,所以後面執行的執行緒都會以最大的協程號作為標記。由此進一步看出

 

三、效能對比

完成時間對比:

threading:平均執行時間:7.0秒

anyncio:平均執行時間3.08秒

 

由上面的多執行緒模組threading和協程模組asyncio的對比可以看出,ansyncio的完成時間是threading的一半左右。由此,asyncio在高併發的情況下具有比較大的優勢,並且在資源的保護上也做得比threading要好。