協程是單線程下的併發,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是協程:協程是一種用戶態的輕量級線程,即協程是由用戶程序本身控制調度的。python
須要強調的是:git
# 1.python的線程屬於內核級別的,即由操做系統控制調度(如單線程遇到io或執行時間過長就會被迫交出cup執行權限,切換其它線程運行) # 2.單線程內開戶協程,一旦遇到io,就會從應用程序級別(而非操做系統)控制切換,以此來提高效率(非io操做的切換與效率無關)
對比操做系統控制線程的切換,用戶在單線程內控制協程的切換github
優勢以下:編程
# 1.協程的切換開銷更小,屬於程序級別的切換,操做系統徹底感知不到,於是更加輕量級 # 2.單線程內就能夠實現併發的效果,最大限度地利用cpu
缺點以下:數組
# 1.協程的本質是單線程,沒法利用多核,能夠是一個程序開啓多個進程,每一個進程內開啓多個線程,每一個線程內開啓協程 # 2.協程指的是單線程,於是一旦協程出現阻塞,將會阻塞整個線程
總結協程特色:併發
1.必須在只有一個單線程裏實現併發異步
2.修改共享數據不須要加鎖async
3.用戶程序裏本身保存多個控制流的上下文棧異步編程
附加:一個協程遇到IO操做自動切換到其它協程(如何實現檢測IO,yield,greenlet都沒法實現,就用到了gevent模塊(select機制))函數
若是咱們在單個線程內有20個任務,要想實如今多個任務之間切換,使用yield生成器的方式過於麻煩(須要先獲得初始化一次的生成器,若是再調用send...很是麻煩),而使用greenlet模塊能夠很是簡單地實現這20個任務直接的切換
安裝:pip install greenlet
#! /usr/bin/env python3 # -*- coding:utf-8 -*- # Author : mayi # Blog : http://www.cnblogs.com/mayi0312/ # Date : 2019/6/3 # Software : PyCharm 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的狀況下或者沒有重複開闢內存空間的操做),反而會下降程序的執行速度
#! /usr/bin/env python3 # -*- coding:utf-8 -*- # Author : mayi # Blog : http://www.cnblogs.com/mayi0312/ # Date : 2019/6/3 # Software : PyCharm # 順序執行 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)) # 15.185868501663208 print("------------------------------------------------") # 切換 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)) # 64.25667524337769
greenlet只是提供了一種比generator更加便捷的切換方式,當一個任務執行時若是遇到io,那就原地阻塞,仍然是沒有解決遇到io自動切換來提高效率的問題。
單線程裏的這20個任務的代碼一般會既有計算操做又有阻塞操做,咱們徹底能夠在執行任務1時遇到阻塞,就利用阻塞的時間去執行任務2...如此,才能提升效率,這就用到了Gevent模塊。
安裝:pip install gevent
Gevent是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程,在gevent中用到的主要模式是Greenlet,這是以C擴展模塊形式接入Python的輕量級協程。Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式的調度。
#! /usr/bin/env python3 # -*- coding:utf-8 -*- # Author : mayi # Blog : http://www.cnblogs.com/mayi0312/ # Date : 2019/6/3 # Software : PyCharm 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('主')
遇到io阻塞時會自動切換任務
上例gevent.sleep(2)模擬的是gevent能夠識別的阻塞,而time.sleep(2)或其它的阻塞,gevent是不能直接識別的,須要用下面一行代碼,打補丁才能夠識別
from gevent import monkey;monkey.patch_all()必須放到被打補丁者的前面,如time,socke模塊導入以前,或者乾脆記憶成:要用gevent,須要將from gevent import monkey;monkey.patch_all()放到文件的開頭
#! /usr/bin/env python3 # -*- coding:utf-8 -*- # Author : mayi # Blog : http://www.cnblogs.com/mayi0312/ # Date : 2019/6/3 # Software : PyCharm from 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) gevent.joinall([g1, g2]) print('主')
#! /usr/bin/env python3 # -*- coding:utf-8 -*- # Author : mayi # Blog : http://www.cnblogs.com/mayi0312/ # Date : 2019/6/3 # Software : PyCharm from gevent import spawn, joinall, monkey;monkey.patch_all() import time def task(pid): """ Some non-deterministic task """ time.sleep(0.5) print('Task %s done' % pid) def synchronous(): for i in range(10): task(i) def asynchronous(): g_l = [spawn(task, i) for i in range(10)] joinall(g_l) if __name__ == '__main__': print('Synchronous:') synchronous() print('Asynchronous:') asynchronous() # 上面程序的重要部分是將task函數封裝到Greenlet內部線程的gevent.spawn。初始 # 化的greenlet列表存放在數組threads中,此數組被傳給gevent.joinall()函數,後 # 者阻塞當前流程,並執行全部給定的greenlet。執行流程只會在全部greenlet執行 # 完後纔會繼續向下走。
#! /usr/bin/env python3 # -*- coding:utf-8 -*- # Author : mayi # Blog : http://www.cnblogs.com/mayi0312/ # Date : 2019/6/3 # Software : PyCharm # Note : 協程應用:爬蟲 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.yahoo.com/'), gevent.spawn(get_page, 'https://github.com/'), ]) stop_time = time.time() print('run time is %s' % (stop_time - start_time))