天天看點

并發程式設計——協程

一、引子

基于單線程來實作并發,即隻用一個主線程(可利用的cpu隻有一個)情況下實作并發(并發的本質:切換+儲存狀态)該怎麼搞。

cpu正在運作一個任務,會在兩種情況下切走去執行其他的任務(切換由作業系統強制控制),一種情況是該任務發生了阻塞,另外一種情況是該任務計算的時間過長或有一個優先級更高的程式替代了它。

在介紹程序理論時,提及程序的三種執行狀态,而線程才是執行機關,是以也可以将上圖了解為線程的三種狀态。

并發程式設計——協程

第一種情況的切換。在任務一遇到io情況下,切到任務二去執行,這樣就可以利用任務一阻塞的時間完成任務二的計算,效率的提升就在于此。

第二種情況的切換并不能提升效率,隻是為了讓cpu能夠雨露均沾,實作看起來所有任務都被“同時”執行的效果,如果多個任務都是純計算的,這種切換反而會降低效率。

為此我們可以基于yield來驗證,yield本身就是一種在單線程下可以儲存任務運作狀态的方法,我們來簡單複習一下:

1 yiled可以儲存狀态,yield的狀态儲存與作業系統的儲存線程狀态很像,但是yield是代碼級别控制的,更輕量級
2 send可以把一個函數的結果傳給另外一個函數,以此實作單線程内程式之間的切換
           

串行執行

import time


def consumer(res):
    """接收資料,處理資料。"""
    print(len(res))
    time.sleep(1)


def producer():
    """生産資料"""
    res = [i for i in range(100000000)]
    return res


if __name__ == '__main__':
    start = time.time()
    result = producer()
    consumer(result)
    stop = time.time()
    print("Time = ", stop - start)

           

輸出結果為:

100000000
Time =  10.884085655212402
           

基于yield并發執行

import time


def consumer():
    """接收資料,處理資料。"""
    res = []
    while True:
        x = yield
        res.append(x)


def producer():
    """生産資料"""
    con = consumer()
    next(con)
    for i in range(100000000):
        con.send(i)


if __name__ == '__main__':
    start = time.time()
    producer()
    stop = time.time()
    print("Time = ", stop - start)

           
Time =  22.830885410308838
           

二、協程介紹

協程:是單線程下的并發,又稱微線程,纖程,英文名Coroutine。

一句話說明什麼是線程:協程是一種使用者态的輕量級線程,即協程是由使用者程式自己控制排程的。

需要強調的是:

1. python的線程屬于核心級别的,即由作業系統控制排程(如單線程遇到io或執行時間過長就會被迫交出cpu執行權限,切換其他線程運作)

2. 單線程内開啟協程,一旦遇到io,就會從應用程式級别(而非作業系統)控制切換,以此來提升效率(!!!非io操作的切換與效率無關)

對比作業系統控制線程的切換,使用者在單線程内控制協程的切換:

優點如下:

  1. 協程的切換開銷更小,屬于程式級别的切換,作業系統完全感覺不到,因而更加輕量級
  2. 單線程内就可以實作并發的效果,最大限度地利用cpu

缺點如下:

  1. 協程的本質是單線程下,無法利用多核,可以是一個程式開啟多個程序,每個程序内開啟多個線程,每個線程内開啟協程
  2. 協程指的是單個線程,因而一旦協程出現阻塞,将會阻塞整個線程

總結協程特點:

必須在隻有一個單線程裡實作并發

修改共享資料不需加鎖

使用者程式裡自己儲存多個控制流的上下文棧
           

三、gevent子產品

Gevent 是一個第三方庫,可以輕松通過gevent實作并發同步或異步程式設計,在gevent中用到的主要模式是Greenlet, 它是以C擴充子產品形式接入Python的輕量級協程。

Greenlet全部運作在主程式作業系統程序的内部,但它們被協作式地排程。

pip install gevent
           
import time
import gevent
import threading
from gevent import monkey


monkey.patch_all()


def eat(name):
    print(name, " is eating on", threading.currentThread().getName())
    time.sleep(2)
    print(name, " is eating done on", threading.currentThread().getName())


def play(name):
    print(name, " is playing on", threading.currentThread().getName())
    gevent.sleep(3)
    print(name, " is playing done on", threading.currentThread().getName())


if __name__ == '__main__':
    start_time = time.time()
    event_1 = gevent.spawn(eat, "Alex")
    event_2 = gevent.spawn(play, "Coco")
    gevent.joinall([event_1, event_2])
    stop_time = time.time()
    print("Time = ", stop_time - start_time)

           
Alex  is eating on DummyThread-1
Coco  is playing on DummyThread-2
Alex  is eating done on DummyThread-1
Coco  is playing done on DummyThread-2
Time =  3.0120625495910645