一、簡介
協程是單執行緒下的并發,又稱微執行緒,纖程,英文名Coroutine,一句話說明什么是協程:協程是一種用戶態的輕量級執行緒,即協程是由用戶程式自己控制調度的,
需要強調的是:
# 1.python的執行緒屬于內核級別的,即由作業系統控制調度(如單執行緒遇到io或執行時間過長就會被迫交出cup執行權限,切換其它執行緒運行) # 2.單執行緒內開戶協程,一旦遇到io,就會從應用程式級別(而非作業系統)控制切換,以此來提升效率(非io操作的切換與效率無關)
對比作業系統控制執行緒的切換,用戶在單執行緒內控制協程的切換
優點如下:
# 1.協程的切換開銷更小,屬于程式級別的切換,作業系統完全感知不到,因而更加輕量級 # 2.單執行緒內就可以實作并發的效果,最大限度地利用cpu
缺點如下:
# 1.協程的本質是單執行緒,無法利用多核,可以是一個程式開啟多個行程,每個行程內開啟多個執行緒,每個執行緒內開啟協程 # 2.協程指的是單執行緒,因而一旦協程出現阻塞,將會阻塞整個執行緒
總結協程特點:
1.必須在只有一個單執行緒里實作并發
2.修改共享資料不需要加鎖
3.用戶程式里自己保存多個控制流的背景關系堆疊
附加:一個協程遇到IO操作自動切換到其它協程(如何實作檢測IO,yield,greenlet都無法實作,就用到了gevent模塊(select機制))
二、Greenlet
如果我們在單個執行緒內有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模塊,
三、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('主')
四、Gevent之同步與異步
#! /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執行 # 完后才會繼續向下走,
五、Gevent之應用舉例
#! /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))
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/186231.html
標籤:Python
上一篇:Python 十大語法
