協程

協程:是單線程下的併發,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是線程:協程是一種用戶態的輕量級線程,即協程是由用戶程序自己控制調度的。協程的本質就是在單線程下,由用戶自己控制一個任務遇到io阻塞了就切換另外一個任務去執行,以此來提升效率。(多線程與多進程是搶佔式的,協程是協作式的)

需要強調的是:
1. python的線程屬於內核級別的,即由操作系統控制調度(如單線程遇到io或執行時間過長就會被迫交出cpu執行權限,切換其他線程運行)

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

對比操作系統控制線程的切換,用戶在單線程內控制協程的切換

優點如下:
1. 協程的切換開銷更小,屬於程序級別的切換,操作系統完全感知不到,因而更加輕量級

2. 單線程內就可以實現併發的效果,最大限度地利用cpu

缺點如下:
1. 協程的本質是單線程下,無法利用多核,可以是一個程序開啓多個進程,每個進程內開啓多個線程,每個線程內開啓協程

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

總結協程特點
1.必須在只有一個單線程裏實現併發
2.修改共享數據不需加鎖
3.用戶程序裏自己保存多個控制流的上下文棧
附加:一個協程遇到IO操作自動切換到其它協程(如何實現檢測IO,yield、greenlet都無法實現,就用到了gevent模塊(select機制))

用yield實現

import time
def customer(name):
    print('%s is ready to eat baozi...'%name)
    while 1:
        baozi=yield
        print('customer %s is eating baozi[%s]'%(name,baozi))
        time.sleep(1)
def producer():
    c1.__next__()
    c2.__next__()
    n=0
    while n<=10:
        print('producer is making baozi...')
        time.sleep(1)
        print('\033[1;32m[producer]\033[0m make baozi[%s] and baozi[%s]'%(n,n+1))
        c1.send(n)
        c2.send(n+1)
        n+=2
if __name__ == '__main__':
    c1=customer('alex')
    c2=customer('bart')
    producer()

用yield實現協程時,遇到io時,仍然會阻塞在原地

如果線程多的話,用yield將非常麻煩於是我們可以使用greenlet模塊

Greenlet

from greenlet import greenlet#導入模塊,如果不存在需要下載

def eat(name):
    print('%s eat 1'%name)
    gr2.switch('bart') #轉換到gr2
    print('%s eat 2'%name)
    gr2.switch()

def play(name):
    print('%s play 1'%name)
    gr1.switch()   #轉換到gr1
    print('%s play 2'%name)

if __name__ == '__main__':
    gr1=greenlet(eat)#傳入要轉換的函數
    gr2=greenlet(play)
    gr1.switch('alex')#可以在第一次switch時傳入參數,以後都不需要
'''
alex eat 1
bart play 1
alex eat 2
bart play 2
'''

單純的切換(在沒有io的情況下或者沒有重複開闢內存空間的操作),反而會降低程序的執行速度

import time

def a():
    n=1
    for i in range(100000):
        n+=i
def m():
    n=1
    for i in range(100000):
        n*=i

s=time.time()
a()
m()
e=time.time()
print('串行時間爲:',e-s)


from greenlet import greenlet
def a():
    n=1
    for i in range(100000):
        n+=i
        g2.switch()
def m():
    n=1
    for i in range(100000):
        n*=i
        g1.switch()
g1=greenlet(a)
g2=greenlet(m)
s=time.time()
g1.switch()
e=time.time()
print('協程時間爲:',e-s)
'''
串行時間爲: 0.03602457046508789
協程時間爲: 0.11908483505249023
'''

greenlet只是提供了一種比generator更加便捷的切換方式,當切到一個任務執行時如果遇到io,那就原地阻塞,仍然是沒有解決遇到IO自動切換來提升效率的問題。
單線程裏的這20個任務的代碼通常會既有計算操作又有阻塞操作,我們完全可以在執行任務1時遇到阻塞,就利用阻塞的時間去執行任務2。。。。如此,才能提高效率,這就用到了Gevent模塊。

Gevent

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(func,1,,2,3,x=4,y=5)創建一個協程對象g1,spawn括號內第一個參數是函數名,如eat,後面可以有多個參數,可以是位置實參或關鍵字實參,都是傳給函數eat的
g1=gevent.spawn(eat,'alex')
g2=gevent.spawn(play,'bart')
gevent.joinall([g1,g2])#相當於g1.join();g2.join()
print('end...')
'''
alex eat 1
bart play 1
bart play 2
alex eat 2
end...
'''
上例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()放到文件的開頭
from gevent import monkey;monkey.patch_all()
import gevent,time

def eat(name):
    print('%s eat 1'%name)
    time.sleep(2)
    print('%s eat 2'%name)

def play(name):
    print('%s play 1'%name)
    time.sleep(1)
    print('%s play 2'%name)

#g1=gevent.spawn(func,1,,2,3,x=4,y=5)創建一個協程對象g1,spawn括號內第一個參數是函數名,如eat,後面可以有多個參數,可以是位置實參或關鍵字實參,都是傳給函數eat的
g1=gevent.spawn(eat,'alex')
g2=gevent.spawn(play,'bart')
gevent.joinall([g1,g2])#相當於g1.join();g2.join()
print('end...')
'''
alex eat 1
bart play 1
bart play 2
alex eat 2
end...
'''

gevent應用(爬蟲)

from gevent import monkey;monkey.patch_all()
import gevent
import requests
import time

def get_page(url):
    print('GET: %s' %url)
    response=requests.get(url)
    if response.status_code == 200:
        print('%d bytes received from %s' %(len(response.text),url))


start_time=time.time()
gevent.joinall([
    gevent.spawn(get_page,'https://www.python.org/'),
    gevent.spawn(get_page,'https://www.baidu.com/'),
    gevent.spawn(get_page,'https://github.com/'),
])
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))
'''
GET: https://www.python.org/
GET: https://www.baidu.com/
GET: https://github.com/
2443 bytes received from https://www.baidu.com/
53719 bytes received from https://github.com/
49273 bytes received from https://www.python.org/
run time is 2.2591280937194824
'''



發佈了51 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章