協程概念
- 協程:是單線程下的併發,又稱微線程。
- 協程是一種用戶態的輕量級線程,即協程是由用戶程序自己控制調度的。
- 1、協程的切換開銷更小,屬於程序級別的切換,操作系統完全感知不到,因而更加輕量級
- 2、單線程內就可以實現併發的效果,最大限度地利用cpu
- 1、協程的本質是單線程下,無法利用多核,可以是一個程序開啟多個進程,每個進程內開啟多個線程,每個線程內開啟協程
- 2、 協程指的是單個線程,因而一旦協程出現阻塞,將會阻塞整個線程
- 1、必須在只有一個單線程裡實現併發
- 2、修改共享數據不需加鎖
- 3、用戶程序裡自己保存多個控制流的上下文棧
- 4、 附加:一個協程遇到IO操作自動切換到其它協程(如何實現檢測IO,yield、greenlet都無法實現,就用到了gevent模塊(select機制))
greenlet
- 需要安裝第三方模塊:pip install greenlet
- 生成器實現協程非常不方便,尤其很多協程的時候,使用greenlet就要方便很多
- 而這個模塊來自於Python的一個衍生版 Stackless Python原生的協程(常見是標準Python是CPython),將其協程單獨拿出來打包成模塊,因此性能要比生成器強很多
- 注意:這裡並沒有解決IO阻塞的問題,但是我們使用這個時間來做別的事情了,一般在工作中我們都是進程+線程+協程的方式來實現併發,以達到最好的併發效果
- 代碼實現
<code>from greenlet import greenlet
import time
def producent():
for i in range(20):
print('生產了%s' % i)
con.switch(i)#切換到消費者
time.sleep(1)#模擬耗時操作,此時greenlet實現切換
print('第%s次生產消費完成'% i)
def consumer():
while True:
var = pro.switch() #等待切換過來和傳入值
print('消費了%s' % var) #在等待的時候可以先乾點其他事情
con = greenlet(consumer) #真正的協程由greenlet實現
pro = greenlet(producent)
con.switch() #消費者進入等待狀態/<code>
gevent
- gevent封裝了epoll和greenlet,在使用的時候要更加方便,同時實現了IO阻塞時的自動切換
- 需要安裝第三方模塊:pip install gevnet
- gevent實現併發服務器
<code>from gevent import monkey;monkey.patch_all() #給gevent模塊打上monkey補丁
import gevent
import socket
server = socket.socket()
server.bind(('127.0.0.1',8989))
server.listen(10000)
def fun_coroutines(conn):
while True:
revc_data = conn.recv(1024)
if revc_data:
print(revc_data)
conn.send(revc_data)
else:
conn.close()
break
while True:
conn,addr = server.accept()
#生成一個協程,並將conn作為參數傳入
gevent.spawn(fun_coroutines,conn)/<code>
gevent應用
- 實現通訊
<code>from gevent import monkey;monkey.patch_all()
from gevent.queue import Queue
import gevent
queue =Queue(3)
def producer(queue):
for i in range(20):
print('生產了%s' % i)
queue.put(i)
def consumer(queue):
for i in range(20):
var = queue.get()
print('消費了%s' % var)
#gevent 自動切換,不需要使用swith去切換
pro = gevent.spawn(producer,queue)
con = gevent.spawn(consumer,queue)
gevent.joinall([pro,con])/<code>
- 實現異步
<code>from gevent import monkey;monkey.patch_all()
from gevent import spawn,joinall
import time
def task(pid):
time.sleep(1) #模擬耗時操作
print('task % 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)#等待所有協程執行完成
print('synchronous:')
start = time.time()
synchronous()
print('synchronous used %s:'%(time.time()-start))
print('asynchronous:')
start1 = time.time()
asynchronous()
print('asynchronous used %s:'%(time.time()-start1))/<code>
- 執行結果如下:
- 由以上可以看出:
- 同步: 所有的方法都是依次執行,總花費時間是所有方法運行之和
- 異步:與同步相對應,異步指的是讓CPU暫時擱置當前請求的響應,處理下一個請求,當通過輪詢或其他方式得到回調通知後,開始運行。
- 多任務是將異步放在子任務中完成。
網絡編程小結
- 一切為了讓CPU忙起來
- 如果阻塞在IO,則可以使用epoll
- 如果一個進程忙計算不過來,對於多核CPU則可以使用多進程,但是開銷大,由操作系統調度
- 如果IO比較密集,則可以使用多線程,因為GIL鎖存在,所以都是併發執行,且要注意資源搶佔情況。開銷適中,由Python解釋器調度
- 協程IO阻塞時,可以自己控制調度到其他的協程,開銷最小,使用gevent可以實現IO異步
閱讀更多 槑孨 的文章