Python協程實踐分享
協程
協程簡單來說就是一個更加輕量級的線程,并且不由操作系統(tǒng)內核管理,完全由程序所控制(在用戶態(tài)執(zhí)行)。協程在子程序內部是可中斷的,然后轉而執(zhí)行其他子程序,在適當的時候返回過來繼續(xù)執(zhí)行。
協程的優(yōu)勢?(協程擁有自己的寄存器上下文和棧,調度切換時,寄存器上下文和棧保存到其他地方,在切換回來的時候,恢復先前保存的寄存器上下文和棧,直接操作棧則基本沒有內核切換的開銷,可以不加鎖的訪問全局變量,所以上下文非???。)
yield在協程中的用法
1、協程中的yield通常出現在表達式的右邊:
x = yield data
如果yield的右邊沒有表達式,默認產出的值是None,現在右邊有表達式,所以返回的是data這個值。
2、協程可以從調用法接受數據,調用通過send(x)方式將數據提供給協程,同時send方法中包含next方法,所以程序會繼續(xù)執(zhí)行。
3、協程可以中斷執(zhí)行,去執(zhí)行另外的協程。
經典示例
代碼:
def hello():
data = "mima"
while True:
x = yield data
print(x)
a = hello()
next(a)
data = a.send("hello")
print(data)代碼詳解:
程序開始執(zhí)行,函數hello不會真的執(zhí)行,而是返回一個生成器給a。
當調用到next()方法時,hello函數才開始真正執(zhí)行,執(zhí)行print方法,繼續(xù)進入while循環(huán);
程序遇到y(tǒng)ield關鍵字,程序再次中斷,此時執(zhí)行到a.send(“hello”)時,程序會從yield關鍵字繼續(xù)向下執(zhí)行,然后又再次進入while循環(huán),再次遇到y(tǒng)ield關鍵字,程序再次中斷;
協程在運行過程中的四個狀態(tài):
- GEN_CREATE:等待開始執(zhí)行
- GEN_RUNNING:解釋器正在執(zhí)行
- GEN_SUSPENDED:在yield表達式處暫停
- GEN_CLOSED:執(zhí)行結束
生產者-消費者模式(協程)
import time
def consumer():
r = ""
while True:
res = yield r
if not res:
print("Starting.....")
return
print("[CONSUMER] Consuming %s...." %res)
time.sleep(1)
r = "200 OK"
def produce(c):
next(c)
n = 0
while n<6:
n+=1
print("[PRODUCER] Producing %s ...."%n)
r = c.send(n)
print("[CONSUMER] Consumer return: %s ...."%r)
c.close()
c = consumer()
produce(c) 代碼分析:
- 調用next©啟動生成器;
- 消費者一旦生產東西,通過c.send切換到消費者consumer執(zhí)行;
- consumer通過yield關鍵字獲取到消息,在通過yield把結果執(zhí)行;
- 生產者拿到消費者處理過的結果,繼續(xù)生成下一條消息;
- 當跳出循環(huán)后,生產者不生產了,通過close關閉消費者,整個過程結束;
gevent第三方庫協程支持
原理:gevent基于協程的Python網絡庫,當一個greenlet遇到IO操作(訪問網絡)自動切換到其他的greenlet等到IO操作完成后,在適當的時候切換回來繼續(xù)執(zhí)行。換而言之就是greenlet通過幫我們自動切換協程,保證有greenlet在運行,而不是一直等待IO操作。
經典代碼
由于切換時在發(fā)生IO操作時自動完成,所以gevent需要修改Python內置庫,這里可以打上猴子補?。ㄓ脕碓谶\行時動態(tài)修改已有的代碼,而不需要原有的代碼)monkey.patch_all
#!/usr/bin/python2
# coding=utf8
from gevent import monkey
monkey.patch_all()
import gevent
import requests
def handle_html(url):
print("Starting %s。。。。" % url)
response = requests.get(url)
code = response.status_code
print("%s: %s" % (url, str(code)))
if __name__ == "__main__":
urls = ["https://www.baidu.com", "https://www.douban.com", "https://www.qq.com"]
jobs = [ gevent.spawn(handle_html, url) for url in urls ]
gevent.joinall(jobs)運行結果:

結果:3個網絡連接并發(fā)執(zhí)行,但是結束的順序不同。
asyncio內置庫協程支持
原理:asyncio的編程模型就是一個消息循環(huán),從asyncio模塊中直接獲取一個Eventloop(事件循環(huán))的應用,然后把需要執(zhí)行的協程放入EventLoop中執(zhí)行,實現異步IO。
經典代碼:
import asyncio
import threading
async def hello():
print("hello, world: %s"%threading.currentThread())
await asyncio.sleep(1) #
print('hello, man %s'%threading.currentThread())
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([hello(), hello()]))
loop.close()
代碼解析:
- 首先獲取一個EventLoop
- 然后將這個hello的協程放進EventLoop,運行EventLoop,它會運行知道future被完成
- hello協程內部執(zhí)行await asyncio.sleep(1)模擬耗時1秒的IO操作,在此期間,主線程并未等待,而是去執(zhí)行EventLoop中的其他線程,實現并發(fā)執(zhí)行。
代碼結果:

異步爬蟲實例:
#!/usr/bin/python3
import aiohttp
import asyncio
async def fetch(url, session):
print("starting: %s" % url)
async with session.get(url) as response:
print("%s : %s" % (url,response.status))
return await response.read()
async def run():
urls = ["https://www.baidu.com", "https://www.douban.com", "http://www.mi.com"]
tasks = []
async with aiohttp.ClientSession() as session:
tasks = [asyncio.ensure_future(fetch(url, session)) for url in urls] # 創(chuàng)建任務
response = await asyncio.gather(*tasks) # 并發(fā)執(zhí)行任務
for body in response:
print(len(response))
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(run())
loop.close()
代碼解析:
- 創(chuàng)建一個事件循環(huán),然后將任務放到時間循環(huán)中;
- run()方法中主要是創(chuàng)建任務,并發(fā)執(zhí)行任務,返回讀取到的網頁內容;
- fetch()方法通過aiohttp發(fā)出指定的請求,以及返回 可等待對象;

(結束輸出網址和list中網址的順序不同,證明協程中異步I/O操作)
關于aiohttp
asyncio實現類TCP、UDP、SSL等協議,aiohttp則是基于asyncio實現的HTTP框架,由此可以用來編寫一個微型的HTTP服務器。
代碼:
from aiohttp import web
async def index(request):
await asyncio.sleep(0.5)
print(request.path)
return web.Response(body=' Hello, World')
async def hello(request):
await asyncio.sleep(0.5)
text = 'hello, %s'%request.match_info['name']
print(request.path)
return web.Response(body=text.encode('utf-8'))
async def init(loop):
app = web.Application(loop=loop)
app.router.add_route("GET", "/" , index)
app.router.add_route("GET","/hello/{name}", hello)
srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)
print("Server started at http://127.0.0.0.1:8000....")
return srv
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(init(loop))
loop.run_forever()
代碼解析:
- 創(chuàng)建一個事件循環(huán),傳入到init協程中;
- 創(chuàng)建Application實例,然后添加路由處理指定的請求;
- 通過loop創(chuàng)建TCP服務,最后啟動事件循環(huán);
到此這篇關于Python協程實踐分享的文章就介紹到這了,更多相關Python協程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
python?解決?pip?時報錯?no?suchoption:?--bulid-dir?的解決辦法(最新
在使用PyCharm虛擬環(huán)境pip時,有時會遇到錯誤提示“no?such?option:?--build-dir”,這可能是由于pip版本不兼容或其他原因導致的,本文將詳細講解如何解決這個問題,感興趣的朋友跟隨小編一起看看吧2023-05-05

