python網絡編程-協程(協程說明,greenlet,gevent)

一:什麼是協程python

  協程(Coroutine):,又稱微線程。協程是一種用戶態的輕量級線程。是由用戶本身控制,CPU根本不知道協程存在。編程

  協程擁有本身的寄存器上下文和棧。數組

  協程調度切換時,將寄存器上下文和棧保存在其餘地方,在切回來的時候,恢復先前保存的寄存器上下文和棧併發

  所以:協程能保留上一次調用的時的狀態,每次過程重入時,就至關於進入上一次調用的。異步

  換種說法:進入上一次離開時所處邏輯流的位置。socket

  注意:線程切換會保存到CPU的寄存器裏。async

  協程的標準:ide

  1)必須在只有一個單線程裏實現併發異步編程

  2)修改共享數據不須要加鎖函數

  3)用戶程序裏本身保存從個控制流的上下文棧

  4)一個協程遇到IO操做自動切換到其餘協程

  

二:協程在何時切換

  在何時進程切換:遇到I/O操做就切換,協程就是把io踢掉了(由於IO耗時)。

   何時切回去: I0操做調用完了,經過調用callback切換回去

 

三:協程的優勢缺點

  優勢:

  1)無需線程上下文切換的開銷

  2)無需原子操做鎖定及同步的開銷(由於協程就是單線程,它就是串行,同一時間改數據只有一個線程)

  3)方便切換控制流,簡化編程模型

  4)高併發+高擴展性+低成本:一個CPU支持上萬的協程不是問題,很適合高併發

  缺點:

  1)沒法利用多核資源:協程本質是單線程,他不能同時單個CPU的多個核用上,協程須要和進程配合

    才能運行在多CPU上。

  2)進行阻塞(Blocking)操做(如IO時)會阻塞整個程序

 

 

四:yield實現切換

  

# -*- coding:utf-8 -*-
__author__ = 'shisanjun'

import time
import queue

def consumer(name):
    print("------->starting eating baozi...")

    while True:
        new_baozi=yield
        print("[%s] is eating baozi %s" %(name,new_baozi))

def producer():
    r=con.__next__() #con=consumer("c1")#只是生成生成器,不會執行,因此先要調用next纔會開始執行
    r=con2.__next__()

    n=0

    while n <5:
        n+=1
        con.send(n)#兩個做業,喚醒生成器,並賦值
        con2.send(n)

        print("\033[32;1m[producer]\033[0m is making baozi %s" %n)

if __name__=='__main__':
    con=consumer("c1") #生成生成器
    con2=consumer("c2")
    p=producer()

"""
------->starting eating baozi...
------->starting eating baozi...
[c1] is eating baozi 1
[c2] is eating baozi 1
[producer] is making baozi 1
[c1] is eating baozi 2
[c2] is eating baozi 2
[producer] is making baozi 2
[c1] is eating baozi 3
[c2] is eating baozi 3
[producer] is making baozi 3
[c1] is eating baozi 4
[c2] is eating baozi 4
[producer] is making baozi 4
[c1] is eating baozi 5
[c2] is eating baozi 5
[producer] is making baozi 5
"""

  咱們剛纔用yield實現一個簡單的協程,實現單線程多併發。

 

 

五:Greenlet

  greenlet是一個用C實現的協程模塊,相比與python自帶的yield,它可使你在任意函數之間隨意切換,而不需把這個函數先聲明爲generator

  

# -*- coding:utf-8 -*-
__author__ = 'shisanjun'

from greenlet import greenlet

def test1():
    print(12)
    gr2.switch()
    print(34)
    gr2.switch()

def test2():
    print(56)
    gr1.switch()
    print(78)

gr1=greenlet(test1)#起動一個協程
gr2=greenlet(test2)
gr1.switch() #從test1開始

 

  上面代碼切換過程

  

   沒有解決一個問題,就是遇到IO操做,自動切換,手動切換。下面實現自動切換

 

 六:Gevent 

   Gevent 是一個第三方庫,能夠輕鬆經過gevent實現併發同步或異步編程,在gevent中用到的主要模式是Greenlet,  

  它是以C擴展模塊形式接入Python的輕量級協程。 Greenlet所有運行在主程序操做系統進程的內部,但它們被協做式地調度。

 

   

# -*- coding:utf-8 -*-
__author__ = 'shisanjun'

import gevent

def func1():
    print("\033[31;1m 李在搞鍺\033[0m")
    gevent.sleep(2)#遇到sleep自動切換,模擬IO操做
    print("\033[31;1m 李在又繼續搞鍺。。。。\033[0m")

def func2():
    print(("\033[32;1m 李切換搞牛。。。\033[0m"))
    gevent.sleep(1)#遇到sleep自動切換
    print(("\033[32;1m 李切換繼續搞牛。。。\033[0m"))


gevent.joinall(
    [
        gevent.spawn(func1),#能夠帶多個參數,第一個爲函數名,第二個爲函數參數
        gevent.spawn(func2)
    ]
)

"""
 李在搞鍺
 李切換搞牛。。。
 李切換繼續搞牛。。。
 李在又繼續搞鍺。。。。
"""

 

 七: 同步與異步的性能區別

 

 

# -*- coding:utf-8 -*-
__author__ = 'shisanjun'

import gevent

def task(pid):
    gevent.sleep(0.5)
    print('Task %s done' %pid)

def synchronous():
    for i in range(1,10):
        task(i)

def asynchronous():
    theads=[gevent.spawn(task,i) for i in range(10)]
    gevent.joinall(theads)

print("synchronous")
synchronous()  #順序執行,結果是一個個出來
print("asynchronous")
asynchronous() #併發執行,結果幾乎同時出來

"""
synchronous
Task 1 done
Task 2 done
Task 3 done
Task 4 done
Task 5 done
Task 6 done
Task 7 done
Task 8 done
Task 9 done
asynchronous
Task 0 done
Task 9 done
Task 8 done
Task 7 done
Task 6 done
Task 5 done
Task 4 done
Task 3 done
Task 2 done
Task 1 done

"""

  上面程序的重要部分是將task函數封裝到Greenlet內部線程的gevent.spawn

   初始化的greenlet列表存放在數組threads中,此數組被傳給gevent.joinall 函數,  

  後者阻塞當前流程,並執行全部給定的greenlet。執行流程只會在 全部greenlet執行完後纔會繼續向下走

   

 八:自動遇到IO切換

  

Gevent 默認不知道urllib,socket作了IO操做,因此打補廳,增長monkey.patch_all()
# -*- coding:utf-8 -*-
__author__ = 'shisanjun'

from gevent import monkey
import gevent
from urllib.request import urlopen
#gevent默認檢測不到ulrlib,因此默認是阻塞的,要加monkey實現自動切換
monkey.patch_all()#實現遇到IO就自動切換

def f(url):
    print('Get %s '%url)
    resp=urlopen(url)#這裏自動切換了

    data=resp.read()
    print("%d bytes received from %s." %(len(data),data))

gevent.joinall([
    gevent.spawn(f,"https://www.baidu.com"),
    gevent.spawn(f,"https://www.360.cn"),


])

 

 九:經過gevent實現單線程下的多socket併發

  

# -*- coding:utf-8 -*-
__author__ = 'shisanjun'
import sys
import socket
import time
import gevent

from gevent import socket,monkey

monkey.patch_all()

def server(port):
    s=socket.socket()
    s.bind(("0.0.0.0",port))
    s.listen(100)

    while True:
        conn,addr=s.accept()
        gevent.spawn(handle_request,conn)

def   handle_request(conn):
    try:
        while True:
            data=conn.recv(1024)
            print('recv:',data)
            conn.send(data)
            if not data:
                conn.shutdown(socket.SHUT_WR)
    except Exception as e:
        print(e)
    finally:
        conn.close()

if __name__=="__main__":
    server(8001)
View Code
# -*- coding:utf-8 -*-
__author__ = 'shisanjun'
import socket
import threading

def sock_conn():

    client = socket.socket()

    client.connect(("localhost",8001))
    count = 0
    while True:
        #msg = input(">>:").strip()
        #if len(msg) == 0:continue
        client.send( ("hello %s" %count).encode("utf-8"))

        data = client.recv(1024)

        print("[%s]recv from server:" % threading.get_ident(),data.decode()) #結果
        count +=1
    client.close()


for i in range(100):
    t = threading.Thread(target=sock_conn)
    t.start()

#併發100個sock鏈接
View Code

 

本文沒有解決:何時切換回來

相關文章
相關標籤/搜索