併發編程【六】協程

協程

 

什麼是協程

協程:是單線程下的併發,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是線程:協程是一種用戶態的輕量級線程,即協程是由用戶程序本身控制調度的。、html

須要強調的是:python

#1. python的線程屬於內核級別的,即由操做系統控制調度(如單線程遇到io或執行時間過長就會被迫交出cpu執行權限,切換其餘線程運行)
#2. 單線程內開啓協程,一旦遇到io,就會從應用程序級別(而非操做系統)控制切換,以此來提高效率(!!!非io操做的切換與效率無關)

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

優勢以下:編程

#1. 協程的切換開銷更小,屬於程序級別的切換,操做系統徹底感知不到,於是更加輕量級
#2. 單線程內就能夠實現併發的效果,最大限度地利用cpu

缺點以下:併發

#1. 協程的本質是單線程下,沒法利用多核,能夠是一個程序開啓多個進程,每一個進程內開啓多個線程,每一個線程內開啓協程
#2. 協程指的是單個線程,於是一旦協程出現阻塞,將會阻塞整個線程

總結協程特色:app

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

greenlet模塊

爲了實現同一線程內不一樣方法中任務能夠來回切換;別人寫的第三方模塊;安裝 :pip3 install greenlet異步

rom greenlet import greenlet

def eat():               # 1
    print('111')         # 6
    g2.switch()          # 7
    print('333')         # 10
    g2.switch()          # 11
def sleep():             # 2
    print('222')         # 8
    g1.switch()          # 9
    print('444')         # 12
g1 = greenlet(eat)       # 3
g2 = greenlet(sleep)     # 4
g1.switch()              # 5

greenlet狀態切換
greenlet狀態切換
進程:計算機中資源分配的最小單位
線程:計算機中cpu調度的最小單位
操做系統負責調度線程,對於操做系統來講,可見的最小單位就是線程;
線程的開銷比進程雖然小的多,可是開啓/關閉線程仍然須要開銷

協程:本質是一條線程,操做系統不可見,是由程序員操做的,而不是由操做系統調度的;

出現的意義:多個任務中的IO時間能夠共享,當執行一個任務遇到IO操做的時候,能夠將程序切換到另外一個任務中繼續執行,在有限的線程中,實現任務的併發,節省了調用操做系統建立/銷燬線程的時間,而且切成的切換效率比線程的切換效率要高,協程執行多個任務可以讓線程少陷入阻塞,讓線程看起來很忙,線程陷入的阻塞的次數越少,那麼可以搶佔cpu的資源就越多,你的程序效率就越高

gevent模塊

安裝:pip3 install geventsocket

Gevent 是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程,在gevent中用到的主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程。 Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度。async

g1=gevent.spawn(func,1,,2,3,x=4,y=5)建立一個協程對象g1,spawn括號內第一個參數是函數名,如eat,後面能夠有多個參數,能夠是位置實參或關鍵字實參,都是傳給函數eat的

g2=gevent.spawn(func2)

g1.join() #等待g1結束

g2.join() #等待g2結束

#或者上述兩步合做一步:gevent.joinall([g1,g2])

g1.value#拿到func1的返回值

用法
用法

gevent只有遇到IO操做纔會發生切換,且IO必須是gevent類中的;ide

複製代碼
# 遇到IO自由切換
import gevent
def eat():  # 協程任務 協程函數
    print('eat start')
    gevent.sleep(1)
    print('eat end')

def sleep(): # 協程任務 協程函數
    print('strat sleep')
    gevent.sleep(1)
    print('end sleep')

g1 = gevent.spawn(eat)
g2 = gevent.spawn(sleep)
# gevent.sleep(1)    # 只有遇到IO操做纔會切換
# g1.join()  # 阻塞,直到g1任務執行完畢
# g2.join()  # 阻塞,直到g2任務執行完畢
gevent.joinall([g1,g2]) # 等於g1.join()+g2.join()
複製代碼

若想使用其餘IO操做須要用from gevent import monkey;monkey.patch_all()  將IO打包 ;

複製代碼
rom gevent import monkey
monkey.patch_all()
import time
import gevent

def eat():    # 協程任務 協程函數
    print('start eating')
    time.sleep(1)
    print('end eating')

def sleep():  # 協程任務 協程函數
    print('start sleeping')
    time.sleep(1)
    print('end sleeping')

g1 = gevent.spawn(eat)   # 建立協程
g2 = gevent.spawn(sleep)
gevent.joinall([g1,g2])  # 阻塞 直到協程任務結束
複製代碼
# 請求網頁
url_dic = {
    '協程':'http://www.cnblogs.com/Eva-J/articles/8324673.html',
    '線程':'http://www.cnblogs.com/Eva-J/articles/8306047.html',
    '目錄':'https://www.cnblogs.com/Eva-J/p/7277026.html',
    '百度':'http://www.baidu.com',
    'sogou':'http://www.sogou.com',
    '4399':'http://www.4399.com',
    '豆瓣':'http://www.douban.com',
    'sina':'http://www.sina.com.cn',
    '淘寶':'http://www.taobao.com',
    'JD':'http://www.JD.com'
}

import time
from gevent import monkey;monkey.patch_all()
from urllib.request import urlopen
import gevent

def get_html(name,url):
    ret = urlopen(url)
    content = ret.read()
    with open(name,'wb') as f:
        f.write(content)

start = time.time()
for name in url_dic:
    get_html(name+'_sync.html',url_dic[name])
ret = time.time() - start
print('同步時間 :',ret)

start = time.time()
g_l = []
for name in url_dic:
    g = gevent.spawn(get_html,name+'_async.html',url_dic[name])
    g_l.append(g)
gevent.joinall(g_l)
ret = time.time() - start
print('異步時間 :',ret)

同步異步時間對比
同步異步時間對比

 

協程實現socket server併發

from gevent import monkey;monkey.patch_all()
import socket
import gevent

sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
def func(conn):
    # print(999)
    while True:
        msg = conn.recv(1024).decode('utf-8')
        print(msg)
        conn.send(b'hello')
while True:
    print(888)
    conn,addr = sk.accept()
    print(000)
    g = gevent.spawn(func, conn)
由於每次鏈接一個客戶端都會進入循環,每次都會阻塞一次 因此才能繼續往下執行

server
server
相關文章
相關標籤/搜索