併發編程 (協程)

一 .協程(Coroutine)

1. 協程簡介

協程:是單線程下的併發,又稱微線程,纖程。英文名Coroutine。一句話說明什麼是協程:協程是一種用戶態的輕量級線程,即協程是由用戶程序本身控制調度的。
#1. python的線程屬於內核級別的,即由操做系統控制調度(如單線程遇到io或執行時間過長就會被迫交出cpu執行權限,切換其餘線程運行)
#2. 單線程內開啓協程,一旦遇到io,就會從應用程序級別(而非操做系統)控制切換,以此來提高效率(!!!非io操做的切換與效率無關)
優勢

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

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

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

注意:進程線程的任務是由操做系統切換完的
協程任務之間是由程序切換 就是(本身寫的代碼)


協程本質就是切換多個任務之間節省一些IO操做(是一種僞線程)
由生成器引出協程

def
cour(): print(11111) x=yield print(x) c=cour() print(c) # 是一個生成器函數 # <generator object cour at 0x0000027DEB9FFF68> # 進程已結束,退出代碼 0 print(***********************************************) import time def cour(): while True: x=yield time.sleep(1) print("處理數據",x) def pud(): c=cour() next(c) for i in range(5): print("生產了數據",i) c.send(i) pud() 生產了數據 0 處理數據 0 生產了數據 1 處理數據 1 生產了數據 2 處理數據 2 生產了數據 3 處理數據 3 生產了數據 4 處理數據 4 進程已結束,退出代碼 0

 2.協程(真正的協程模塊就是使用greenlet完成切換的)

from  greenlet import greenlet

def eat():
    print("開始吃飯了哈哈哈哈")
    g2.switch()   # 切換到g2執行
    print("飯吃完了哈哈哈哈")


def play():
    print("開始跑了哈哈哈啊哈")
g1=greenlet(eat)      # greenlet 表示開啓協程
g2=greenlet(play)     # greenlet 表示開啓協程
g1.switch()      # switch()    表示g1開始執行


# 開始吃飯了哈哈哈哈
# 開始跑了哈哈哈啊哈
# 進程已結束,退出代碼 0
 
 
 注意協程本身程序調用 (就是本身寫的代碼調度執行) 不是由cpu或者操做系統調用
 
from  greenlet import greenlet
def eat():
print("開始吃飯了哈哈哈哈")
g2.switch() # 切換到g2執行
print("飯吃完了哈哈哈哈")
g2.switch() # 切換到g2執行

def play():
print("8888888888888888")
g1.switch() # 切換到g1執行
print("9999999999999999")


g1=greenlet(eat) # greenlet 表示開啓協程
g2=greenlet(play) # greenlet 表示開啓協程
g1.switch() # switch() 表示g1開始執行

開始吃飯了哈哈哈哈
8888888888888888
飯吃完了哈哈哈哈
9999999999999999python



from
greenlet import greenlet def eat(): print("開始吃飯了哈哈哈哈") g2.switch() # 切換到g2執行 print("飯吃完了哈哈哈哈") g2.switch() # 切換到g2執行 def play(): print("8888888888888888") g1.switch() # 切換到g1執行 print("9999999999999999") g1.switch() # 切換到g1執行 g1=greenlet(eat) # greenlet 表示開啓協程 g2=greenlet(play) # greenlet 表示開啓協程 g1.switch() # switch() 表示g1開始執行 g2.switch() # switch() 表示g1開始執行 開始吃飯了哈哈哈哈 8888888888888888 飯吃完了哈哈哈哈 9999999999999999 進程已結束,退出代碼

 3.協程(效率比較)單純的切換(在沒有io的狀況下或者沒有重複開闢內存空間的操做)

          ,反而會下降程序的執行速度

按照順序執行時間
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)) #10.985628366470337
按照協程切換執行時間
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)) # 52.763017892837524

注意
這裏面時間包含了切換時間 執行時間 切換時間大概4幾0秒左右 執行時間大概10幾秒左右

                                                            

4.協程Gevent(主要模式是Greenlet, 它是以C擴展模塊形式接入Python的輕量級協程)

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.sleep(2)模擬的是gevent能夠識別的io阻塞,
# 而time.sleep(2)或其餘的阻塞,gevent是不能直接識別的須要用下面一行代碼,打補丁,就能夠識別了
# from gevent import monkey;monkey.patch_all()
import  gevent,time
def eat():
    print("開始吃飯了哈哈哈哈")
    gevent.sleep(1)
    # time.sleep(1)
    print("飯吃完了哈哈哈哈")

def play():
    print("8888888888888888")
    gevent.sleep(1)
    # time.sleep(1)
    print("9999999999999999")


g1=gevent.spawn(eat)      # gevent.spawn表示開啓協程
g2=gevent.spawn(play)     # gevent.spawn表示開啓協程
g1.join()  等待結束
g2.join()  等待結束


開始吃飯了哈哈哈哈
飯吃完了哈哈哈哈
8888888888888888
9999999999999999

進程已結束,退出代碼 0
# 如time,socket模塊以前 # 或者咱們乾脆記憶成:要用gevent,須要將from gevent import monkey;monkey.patch_all()放到文件的開頭

注意:進程線程的任務是由操做系統切換完的
協程任務之間是由程序切換 就是(本身寫的代碼)只有遇到協程模塊識別IO操做時候 程序纔會進行切換 實現併發
import gevent import time from gevent import monkey;monkey.patch_all() def eat(): print("開始吃飯了哈哈哈哈") time.sleep(1) print("飯吃完了哈哈哈哈") def play(): print("8888888888888888") time.sleep(1) print("9999999999999999") g1=gevent.spawn(eat) # gevent.spawn表示開啓協程 g2=gevent.spawn(play) # gevent.spawn表示開啓協程 g1.join() g2.join()
import  gevent
import  time
from  gevent import monkey;monkey.patch_all() 
import threading
def eat():
    print(threading.current_thread().getName())   DummyThread-1 DummyThread-1 表示 僞類型線程 就是僞造出來的 print("開始吃飯了哈哈哈哈")
    time.sleep(1)
    print("飯吃完了哈哈哈哈")

def play():
    print("8888888888888888")
    time.sleep(1)
    print("9999999999999999")


g1=gevent.spawn(eat)      # gevent.spawn表示開啓協程
g2=gevent.spawn(play)     # gevent.spawn表示開啓協程
g1.join()
g2.join()

DummyThread-1          # DummyThread-1 表示 僞類型線程 就是僞造出來的
開始吃飯了哈哈哈哈
8888888888888888
飯吃完了哈哈哈哈
9999999999999999

5. Gevent之同步與異步

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)
    print('DONE')
    
if __name__ == '__main__':
    print('Synchronous:')
    synchronous()
    print('Asynchronous:')
    asynchronous()
#  上面程序的重要部分是將task函數封裝到Greenlet內部線程的gevent.spawn。
#  初始化的greenlet列表存放在數組threads中,此數組被傳給gevent.joinall 函數,
#  後者阻塞當前流程,並執行全部給定的greenlet任務。執行流程只會在 全部greenlet執行完後纔會繼續向下走。

 6.協程的應用(用於高網絡延時 和爬蟲)

爬蟲案例

#
如time,socket模塊以前 # 或者咱們乾脆記憶成:要用gevent,須要將from gevent import monkey;monkey.patch_all()放到文件的開頭 # from gevent import spawn, joinall, monkey;monkey.patch_all() import gevent import time from urllib.request import urlopen def get_date(url): res=urlopen(url) cc=res.read().decode("utf-8") return len(cc) g1=gevent.spawn(get_date,"https://www.cnblogs.com/") g2=gevent.spawn(get_date,"https://www.sina.com.cn/") g3=gevent.spawn(get_date,"https://www.cnblogs.com/") g4=gevent.spawn(get_date,"https://www.cnblogs.com/") g5=gevent.spawn(get_date,"https://www.cnblogs.com/") aa=gevent.joinall([g1,g2,g3,g4,g5]) for i in aa: print(i.value) # 41781 # 41781 # 41781 # 535821 # 41781 # # 進程已結束,退出代碼 0

  使用協程實現socket

sever端 使用協程實現socket

#
如time,socket模塊以前 # 或者咱們乾脆記憶成:要用gevent,須要將from gevent import monkey;monkey.patch_all()放到文件的開頭 from gevent import spawn, joinall, monkey;monkey.patch_all() import gevent import time import socket def msg(conn): while True: aa=conn.recv(1024).decode("utf-8") print(aa) bb=input("接收:") conn.send(bb.encode("utf-8")) server=socket.socket() server.bind(("127.0.0.1",8700)) server.listen(4) while True: conn,addr=server.accept() g1=gevent.spawn(msg,conn)
client端 import  socket
cli=socket.socket()
cli.connect(("127.0.0.1",8700))
while True:
   aa=input("發送:")
   cli.send(aa.encode("utf-8"))
   cl= cli.recv(1024).decode("utf-8")
   print(cl)
相關文章
相關標籤/搜索