天天看點

python并發之協程_并發程式設計之協程

儲備知識:

并發指的是多個任務看起來是同時運作的,并發實作的本質:切換和儲存狀态

并發:看起來是同時運作,切換+儲存狀态

并行:真正意義上的同時運作,隻有在多CPU的情況下才能實作并行

串行:一個任務完完整整的執行完畢才能運作下一個任務

如果多個任務都是純計算,那麼并發的切換反而會降低效率----使用yiled驗證。yiled本身就是一種單線程下儲存任務運作狀态的方法。

#1 yiled可以儲存狀态,yield的狀态儲存與作業系統的儲存線程狀态很像,但是yield是代碼級别控制的,更輕量級

#2 send可以把一個函數的結果傳給另外一個函數,以此實作單線程内程式之間的切換

python并發之協程_并發程式設計之協程
python并發之協程_并發程式設計之協程

# 純計算的任務串行執行

import time

def task1():

res=1

for i in range(1000000):

res+=i

def task2():

res=1

for i in range(1000000):

res*=i

start=time.time()

#基于yield儲存狀态,實作兩個任務直接來回切換,即并發的效果

#PS:如果每個任務中都加上列印,那麼明顯地看到兩個任務的列印是你一次我一次,即并發執行的.

task1()

task2()

stop=time.time()

print(stop-start)

# 純計算的任務并發執行

import time

def task1():

res=1

for i in range(1000000):

res+=i

yield

time.sleep(10000)

print('task1')

def task2():

g=task1()

res=1

for i in range(1000000):

res*=i

next(g)

print('task2')

start=time.time()

#基于yield儲存狀态,實作兩個任務直接來回切換,即并發的效果

#PS:如果每個任務中都加上列印,那麼明顯地看到兩個任務的列印是你一次我一次,即并發執行的.

task2()

stop=time.time()

print(stop-start)

yiled執行個體

python并發之協程_并發程式設計之協程
python并發之協程_并發程式設計之協程

import time

def consumer():

'''任務1:接收資料,處理資料'''

while True:

x=yield

def producer():

'''任務2:生産資料'''

g=consumer()

next(g)

for i in range(10000000):

g.send(i)

time.sleep(2)

start=time.time()

producer() #并發執行,但是任務producer遇到io就會阻塞住,并不會切到該線程内的其他任務去執行

stop=time.time()

print(stop-start)

yoled不能實作遇到IO切換

協程

單線程下的并發,叫做協程

協程是一個使用者态的輕量級線程,即協程是有使用者程式自己控制排程的。

強調

#1. python的線程屬于核心級别的,即由作業系統控制排程(如單線程遇到io或執行時間過長就會被迫交出cpu執行權限,切換其他線程運作)

#2. 單線程内開啟協程,一旦遇到io,就會從應用程式級别(而非作業系統)控制切換,以此來提升效率(!!!非io操作的切換與效率無關)

對比作業系統控制線程的切換,使用者在單線程内控制協程的切換

優點:

#1. 協程的切換開銷更小,屬于程式級别的切換,作業系統完全感覺不到,因而更加輕量級

#2. 單線程内就可以實作并發的效果,最大限度地利用cpu

缺點:

#1. 協程的本質是單線程下,無法利用多核,可以是一個程式開啟多個程序,每個程序内開啟多個線程,每個線程内開啟協程

#2. 協程指的是單個線程,因而一旦協程出現阻塞,将會阻塞整個線程

Greenlet子產品(單純的切換,隻是比yiled友善,同樣不能做到遇到IO切換)

from greenlet import greenlet

def eat(name):

print('%s eat 1' %name)

g2.switch('egon')

print('%s eat 2' %name)

g2.switch()

def play(name):

print('%s play 1' %name)

g1.switch()

print('%s play 2' %name)

g1=greenlet(eat)

g2=greenlet(play)

g1.switch('egon')#可以在第一次switch時傳入參數,以後都不需要

單純的切換(在沒有IO的情況下)反而會降低程式的執行速度

python并發之協程_并發程式設計之協程
python并發之協程_并發程式設計之協程

#順序執行

import time

def f1():

res=1

for i in range(100000000):

res+=i

def f2():

res=1

for i in range(100000000):

res*=i

start=time.time()

f1()

f2()

stop=time.time()

print('run time is %s' %(stop-start)) #10.985628366470337

#切換

from greenlet import greenlet

import time

def f1():

res=1

for i in range(100000000):

res+=i

g2.switch()

def f2():

res=1

for i in range(100000000):

res*=i

g1.switch()

start=time.time()

g1=greenlet(f1)

g2=greenlet(f2)

g1.switch()

stop=time.time()

print('run time is %s' %(stop-start)) # 52.763017892837524

Greenlet

Gevent第三方庫(實作遇到IO阻塞自動切換)

#用法

g1=gevent.spawn(func,1,,2,3,x=4,y=5)建立一個協程對象g1,spawn括号内第一個參數是函數名,如eat,後面可以有多個參數,可以是位置實參或關鍵字實參,都是傳給函數eat的

g2=gevent.spawn(func2)

g1.join() #等待g1結束

g2.join() #等待g2結束

#或者上述兩步合作一步:gevent.joinall([g1,g2])

g1.value#拿到func1的傳回值

遇到io自動切換

python并發之協程_并發程式設計之協程
python并發之協程_并發程式設計之協程

import gevent

def eat(name):

print('%s eat 1' %name)

gevent.sleep(2)

print('%s eat 2' %name)

def play(name):

print('%s play 1' %name)

gevent.sleep(1)

print('%s play 2' %name)

g1=gevent.spawn(eat,'egon')

g2=gevent.spawn(play,name='egon')

g1.join()

g2.join()

#或者gevent.joinall([g1,g2])

print('主')

View Code

上例gevent.sleep(2)模拟的是gevent可以識别的io阻塞,

而time.sleep(2)或其他的阻塞,gevent是不能直接識别的需要用下面一行代碼,打更新檔,就可以識别了

from gevent import monkey;monkey.patch_all()必須放到被打更新檔者的前面,如time,socket子產品之前

或者我們幹脆記憶成:要用gevent,需要将from gevent import monkey;monkey.patch_all()放到檔案的開頭

rom gevent import monkey;monkey.patch_all()

import gevent

import time

def eat():

print('eat food 1')

time.sleep(2)

print('eat food 2')

def play():

print('play 1')

time.sleep(1)

print('play 2')

g1=gevent.spawn(eat)

g2=gevent.spawn(play_phone)

gevent.joinall([g1,g2])

print('主')

我們可以用threading.current_thread().getName()來檢視每個g1和g2,檢視的結果為DummyThread-n,即假線程

希望與廣大網友互動??

點此進行留言吧!