Python的協程

昀溪發表於2018-08-26

什麼是協程

協程又叫做微執行緒,它是在單一執行緒內透過不斷切換執行的。協程的切換不是上下文的切換也就是說不是CPU的執行任務的切換,比如CPU執行一會執行緒1,然後再執行一會執行緒2,在多核CPU上,Python由於有GIL,所以它的切換是核心1上的執行緒1執行一會,然後核心2上的執行緒2執行一會。協程是單執行緒的也就是執行緒這種東西是在單一執行緒內部,協程的切換也是線上程內部切換的,它切換的是執行流,所以本質上執行緒內的所有協程執行是順序的那也就是意味著某個協程阻塞會阻塞整個執行緒,這也就是為什麼說協程切換沒有開銷同時不需要鎖的原因,同時也就是說明了協程本身無法利用多核資源,因為它依附於執行緒而執行緒本身它自己也只能跑在一個核心上,那麼要想併發就只能多程式加協程。

還記的之前的例子麼

#!/usr/bin/env python
# -*- coding: utf-8 -*-


def consumer(name, pices):
    print("--->[%s]等待骨頭,請餵我 %d 塊。" % (name, pices))
    eaten = 0
    while True:
        # 第一次呼叫這個函式將返回一個生成器而不是真正執行這裡面的方法,只有調這個生成器的next方法才會執行。
        # yield 可以返回資料,我這裡沒有返回。程式走到這裡遇到yield就返回了,這裡為什麼賦值給一個變數呢,因為可以接收資料。
        # 返回後程式就停在這裡了。只有當喚醒send之後才會執行下面的列印語句。
        bone = yield
        if eaten == pices:
            print("[%s] 我已經吃飽了。" % name)
        else:
            print("[%s] 吃了 %s 塊骨頭。" % (name, bone))
            eaten += 1
            print("[%s] 我已經吃了 %d 塊骨頭。" % (name, eaten))
        # time.sleep(1)


def producer():
    # 透過對生成器呼叫 next()的時候才會執行
    next(petDog1)
    next(petDog2)
    n = 0
    while n < 10:
        n += 1
        print("\033[32;1m[主人]\033[0m 丟 %s 塊骨頭。" % 1)
        # send是喚醒生成器,也就是讓函式繼續執行,這裡輸入一個引數,表示啟用這個生成器的時候給它傳遞一個變數進去,本例就是上面的 bone 這個變數,丟一塊骨頭
        petDog1.send(1)
        petDog2.send(1)


if __name__ == '__main__':
    # 函式里面有 yield 第一次呼叫這個函式它返回的是一個生成器,所以第一次不執行
    petDog1 = consumer("金毛", 10)
    petDog2 = consumer("泰迪", 3)
    master = producer()

這個使用了yeild的例子就是協程的概念。透過yeild定義生成器,透過next和send不斷在兩個狗之間切換。這裡呢其實是手動切換在實際當中應該是自動切換,因為每個狗吃骨頭的時間不同,大狗吃的快小狗吃的慢。

gevent

這是一個第三方庫,實現了單一執行緒內的多個執行流協作排程。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com

def task1():
    print('\033[31;1m task1 \033[0m running...')
    gevent.sleep(2)  # 模仿IO操作的時間
    print('\033[31;1m task1 \033[0m continue running...')


def task2():
    print('\033[32;1m task2 \033[0m running...')
    gevent.sleep(1)  # 模仿IO操作的時間
    print('\033[32;1m task2 \033[0m continue running...')


gevent.joinall([
    gevent.spawn(task1),
    gevent.spawn(task2),
])

這個過程它實現了在兩個任務之間進行切換而且是自動的並沒有手動控制。

spawn實現了事件註冊,而join實現了事件的輪詢。透過gevent加上socket可以實現併發非同步socket。

對gevent詳細說明

對greenlet詳細說明

相關文章