Python併發編程(七):協程

一、引言

本節的主題是基於單線程來實現併發,即只用一個主線程(很明顯可利用的cpu只有一個)情況下實現併發,爲此我們需要先回顧下併發的本質:切換+保存狀態

cpu正在運行一個任務,會在兩種情況下切走去執行其他的任務(切換由操作系統強制控制),一種情況是該任務發生了阻塞(有IO請求),另外一種情況是該任務計算的時間過長(時間片到)
我們不能優化時間片到這種系統層面的規定好的,但是IO請求是我們程序的設置好的,我們可以檢測的,所以我們就可以對其進行優化!

二、協程

在一個任務遇到io情況下,IO開始後,進入阻塞,便切到任務二去執行,這樣就可以利用任務一阻塞的時間完成任務二的計算,效率的提升就在於此。這也是協程的思想,旨在最大限度利用cpu!

根據目前學習的知識我們可以使用yield可以實現切換,但是不能實現檢測IO進行切換

import time
def func1():
    while True:
        print('func1')
        yield

def func2():
    g=func1()
    for i in range(10000000):
        i+1
        next(g)
        time.sleep(3) # 該sleep的還是會sleep,並不會利用sleep的這段時間纔去切換,所以這樣並沒有什麼用
        print('func2')
start=time.time()
func2()
stop=time.time()
print(stop-start)
# 協程的要點:
1. 可以控制多個任務之間的切換,'切換之前將任務的狀態保存下來',以便重新運行時,可以基於暫停的位置繼續執行。
2. 作爲1的補充:可以檢測io操作,'在遇到io操作的情況下才發生切換'

三、協程的優缺點

# 優點
1. 協程的切換開銷更小,屬於程序級別的切換,操作系統完全感知不到,因而更加輕量級
2. 單線程內就可以實現併發的效果,最大限度地利用cpu
# 缺點
1. 協程的本質是單線程下,無法利用多核。但是可以是一個程序開啓多個進程,每個進程內開啓多個線程,每個線程內開啓協程
2. 協程指的是單個線程,因而一旦協程出現阻塞,將會阻塞多個線程,'因此協程必須能檢測所有的IO!'

四、協程的實現(Gevent模塊)

# mac本安裝gevent模塊命令
pip3 install gevent

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

import gevent
def eat(name):
    print('%s eat 1' %name)
    gevent.sleep(2)
    print('%s eat 2' %name)

def play(name):
    print('%s play 1' %name)
    gevent.sleep(1)
    print('%s play 2' %name)


g1=gevent.spawn(eat,'egon')
g2=gevent.spawn(play,name='egon')
g1.join()
g2.join()
#或者gevent.joinall([g1,g2])
print('主')

上例gevent.sleep(2)模擬的是gevent可以識別的io阻塞。

time.sleep(2)或其他的阻塞,gevent是不能直接識別的需要打補丁,就可以識別了

from gevent import monkey;monkey.patch_all() # 打個猴子補丁

import gevent
import time
def eat():
    print('eat food 1')
    time.sleep(2)
    print('eat food 2')

def play():
    print('play 1')
    time.sleep(1)
    print('play 2')

g1=gevent.spawn(eat)
g2=gevent.spawn(play_phone)
gevent.joinall([g1,g2])
print('主')

如果不瞭解猴子補丁請看:https://blog.csdn.net/weixin_44571270/article/details/106120605

五、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(): # 同步代碼,與異步的gevent做對比
    for i in range(10):
        task(i)

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

if __name__ == '__main__':
    print('Synchronous:')
    synchronous()

    print('Asynchronous:')
    asynchronous()
# 上面程序的重要部分是將task函數封裝到Greenlet內部線程的gevent.spawn。 初始化的greenlet列表存放在數組threads中,此數組被傳給gevent.joinall 函數,後者阻塞當前流程,並執行所有給定的greenlet。執行流程只會在 所有greenlet執行完後纔會繼續向下走。

六、進程、線程、協程使用方法

一個程序開啓多個進程,每個進程內開啓多個線程,每個線程內開啓協程
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章