黄色网页视频 I 影音先锋日日狠狠久久 I 秋霞午夜毛片 I 秋霞一二三区 I 国产成人片无码视频 I 国产 精品 自在自线 I av免费观看网站 I 日本精品久久久久中文字幕5 I 91看视频 I 看全色黄大色黄女片18 I 精品不卡一区 I 亚洲最新精品 I 欧美 激情 在线 I 人妻少妇精品久久 I 国产99视频精品免费专区 I 欧美影院 I 欧美精品在欧美一区二区少妇 I av大片网站 I 国产精品黄色片 I 888久久 I 狠狠干最新 I 看看黄色一级片 I 黄色精品久久 I 三级av在线 I 69色综合 I 国产日韩欧美91 I 亚洲精品偷拍 I 激情小说亚洲图片 I 久久国产视频精品 I 国产综合精品一区二区三区 I 色婷婷国产 I 最新成人av在线 I 国产私拍精品 I 日韩成人影音 I 日日夜夜天天综合

python協(xié)程詳解

系統(tǒng) 1903 0

目錄

  • python協(xié)程詳解
    • 一、什么是協(xié)程
    • 二、了解協(xié)程的過程
      • 1、yield工作原理
      • 2、預(yù)激協(xié)程的裝飾器
      • 3、終止協(xié)程和異常處理
      • 4、讓協(xié)程返回值
      • 5、yield from的使用
      • 6、yield from的意義
    • 三、greenlet的使用
    • 四、gevent的使用

python協(xié)程詳解

一、什么是協(xié)程

協(xié)程又稱為微線程,協(xié)程是一種用戶態(tài)的輕量級線程

協(xié)程擁有自己的寄存器和棧。協(xié)程調(diào)度切換的時候,將寄存器上下文和棧都保存到其他地方,在切換回來的時候,恢復(fù)到先前保存的寄存器上下文和棧,因此:協(xié)程能保留上一次調(diào)用狀態(tài),每次過程重入時,就相當(dāng)于進(jìn)入上一次調(diào)用的狀態(tài)。

協(xié)程的好處:

  • 1.無需線程上下文切換的開銷(還是單線程)

  • 2.無需原子操作(一個線程改一個變量,改一個變量的過程就可以稱為原子操作)的鎖定和同步的開銷

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

  • 4.高并發(fā)+高擴(kuò)展+低成本:一個cpu支持上萬的協(xié)程都沒有問題,適合用于高并發(fā)處理

缺點(diǎn):

  • 1.無法利用多核的資源,協(xié)程本身是個單線程,它不能同時將單個cpu的多核用上,協(xié)程需要和進(jìn)程配合才能運(yùn)用到多cpu上(協(xié)程是跑在線程上的)

  • 2.進(jìn)行阻塞操作時會阻塞掉整個程序:如io

二、了解協(xié)程的過程

1、yield工作原理

從語法上來看,協(xié)程和生成器類似,都是定義體中包含yield關(guān)鍵字的函數(shù)。
yield在協(xié)程中的用法:

  • 在協(xié)程中yield通常出現(xiàn)在表達(dá)式的右邊,例如:datum = yield,可以產(chǎn)出值,也可以不產(chǎn)出--如果yield關(guān)鍵字后面沒有表達(dá)式,那么生成器產(chǎn)出None。
  • 在協(xié)程中yield也可能從調(diào)用方接受數(shù)據(jù),調(diào)用方是通過send(datum)的方式把數(shù)據(jù)提供給協(xié)程使用,而不是next(...)函數(shù),通常調(diào)用方會把值推送給協(xié)程。
  • 協(xié)程可以把控制器讓給中心調(diào)度程序,從而激活其他的協(xié)程。

所以總體上在協(xié)程中把yield看做是控制流程的方式。

先通過一個簡單的協(xié)程的例子理解:

          
            def simple_demo():
    print("start")
    x = yield
    print("x:", x)

sd = simple_demo()
next(sd)
sd.send(10)

---------------------------

>>> start
>>> x: 10
>>> Traceback (most recent call last):
>>>   File "D:/python_projects/untitled3/xiecheng1.py", line 9, >>> in 
            
              
>>>     sd.send(10)
>>> StopIteration
            
          
        

對上述例子的分析:

yield 的右邊沒有表達(dá)式,所以這里默認(rèn)產(chǎn)出的值是None
剛開始先調(diào)用了next(...)是因?yàn)檫@個時候生成器還沒有啟動,沒有停在yield那里,這個時候也是無法通過send發(fā)送數(shù)據(jù)。所以當(dāng)我們通過next(...)激活協(xié)程后,程序就會運(yùn)行到x = yield,這里有個問題我們需要注意,x = yield這個表達(dá)式的計算過程是先計算等號右邊的內(nèi)容,然后在進(jìn)行賦值,所以當(dāng)激活生成器后,程序會停在yield這里,但并沒有給x賦值。

當(dāng)我們調(diào)用send方法后yield會收到這個值并賦值給x,而當(dāng)程序運(yùn)行到協(xié)程定義體的末尾時和用生成器的時候一樣會拋出StopIteration異常

如果協(xié)程沒有通過next(...)激活(同樣我們可以通過send(None)的方式激活),但是我們直接send,會提示如下錯誤:

          
            def simple_demo():
    print("start")
    x = yield
    print("x:", x)

sd = simple_demo()
# next(sd)
sd.send(10)

---------------------------

>>> Traceback (most recent call last):
>>>   File "D:/python_projects/untitled3/xiecheng1.py", line 9, >>> in 
            
              
>>>     sd.send(10)
>>> TypeError: can't send non-None value to a just-started generator
            
          
        

關(guān)于調(diào)用next(...)函數(shù)這一步通常稱為”預(yù)激(prime)“協(xié)程,即讓協(xié)程向前執(zhí)行到第一個yield表達(dá)式,準(zhǔn)備好作為活躍的協(xié)程使用

協(xié)程在運(yùn)行過程中有四個狀態(tài):

  • GEN_CREATE:等待開始執(zhí)行
  • GEN_RUNNING:解釋器正在執(zhí)行,這個狀態(tài)一般看不到
  • GEN_SUSPENDED:在yield表達(dá)式處暫停
  • GEN_CLOSED:執(zhí)行結(jié)束

通過下面例子來查看協(xié)程的狀態(tài):

          
            >>> from inspect import getgeneratorstate
>>> def simple_demo(a):
    print("start: a = ", a)
    b = yield a
    print("b = ", b)
    c = yield a + b
    print("c = ", c)

    
>>> sd = simple_demo(2)
>>> print(getgeneratorstate(sd))
GEN_CREATED
>>> next(sd)  # 預(yù)激協(xié)程,使它走到第一個yield處,因?yàn)榈谝粋€yield處有yield值a,所以返回a的值,然后在此yield處阻塞
start: a =  2
2
>>> print(getgeneratorstate(sd))
GEN_SUSPENDED
>>> sd.send(3) # 發(fā)送3,進(jìn)入?yún)f(xié)程接著上一次阻塞的yield處執(zhí)行,yield接收參數(shù)3賦值給b,到下一個yield處返回a+b的值,然后在此yield處再次阻塞,等待下次send值
b =  3
5
>>> sd.send(4) # 同上一次send過程,到此結(jié)束拋異常
c =  4
Traceback (most recent call last):
  File "
            
              ", line 1, in 
              
                
    sd.send(4)
StopIteration
>>> print(getgeneratorstate(sd))
GEN_CLOSED
              
            
          
        

可以通過注釋理解這個例子。

接著再通過一個計算平均值的例子來繼續(xù)理解:

          
            >>> def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total/count

        
>>> avg = averager()
>>> next(avg)
>>> avg.send(10)
10.0
>>> avg.send(30)
20.0
>>> avg.send(40)
26.666666666666668
          
        

這里是一個死循環(huán),只要不停send值給協(xié)程,可以一直計算下去。
通過上面的幾個例子我們發(fā)現(xiàn),我們?nèi)绻胍_始使用協(xié)程的時候必須通過next(...)方式激活協(xié)程,如果不預(yù)激,這個協(xié)程就無法使用,如果哪天在代碼中遺忘了那么就出問題了,所以有一種預(yù)激協(xié)程的裝飾器,可以幫助我們干這件事。

2、預(yù)激協(xié)程的裝飾器

下面是預(yù)激裝飾器的演示例子:

          
            from functools import wraps

def coroutine(func):
    @wraps(func)
    def primer(*args,**kwargs):
        gen = func(*args,**kwargs)
        next(gen)
        return gen
    return primer

@coroutine
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield average
        total += term
        count += 1
        average = total/count

coro_avg = averager()
from inspect import getgeneratorstate
print(getgeneratorstate(coro_avg))
print(coro_avg.send(10))
print(coro_avg.send(30))
print(coro_avg.send(5))

---------------------------

>>> GEN_SUSPENDED
>>> 10.0
>>> 20.0
>>> 15.0
          
        

關(guān)于預(yù)激,在使用yield from句法調(diào)用協(xié)程的時候,會自動預(yù)激活,這樣其實(shí)與我們上面定義的coroutine裝飾器是不兼容的,在python3.4里面的asyncio.coroutine裝飾器不會預(yù)激協(xié)程,因此兼容yield from

3、終止協(xié)程和異常處理

協(xié)程中未處理的異常會向上冒泡,傳給 next 函數(shù)或 send 方法的調(diào)用方(即觸發(fā)協(xié)程的對象)。

繼續(xù)使用上面averager的例子

          
            >>> coro_avg = averager()
>>> coro_avg.send(40)
40.0
>>> coro_avg.send(50)
45.0
>>> coro_avg.send('spam')
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +=: 'float' and 'str'
>>> coro_avg.send(60)
Traceback (most recent call last):
File "
            
              ", line 1, in 
              
                
StopIteration
              
            
          
        

由于在協(xié)程內(nèi)沒有處理異常,協(xié)程會終止。如果試圖重新激活協(xié)程,會拋出StopIteration 異常。

從 Python 2.5 開始,客戶代碼可以在生成器對象上調(diào)用兩個方法:throw 和 close,顯式地把異常發(fā)給協(xié)程。

  • 1:generator.throw(exc_type[, exc_value[, traceback]])

使生成器在暫停的 yield 表達(dá)式處拋出指定的異常。如果生成器處理了拋出的異常,代碼會向前執(zhí)行到下一個 yield 表達(dá)式,而產(chǎn)出的值會成為調(diào)用 generator.throw方法得到的返回值。如果生成器沒有處理拋出的異常,異常會向上冒泡,傳到調(diào)用方的上下文中。

  • 2:generator.close()

使生成器在暫停的 yield 表達(dá)式處拋出 GeneratorExit 異常。如果生成器沒有處理這個異常,或者拋出了 StopIteration 異常(通常是指運(yùn)行到結(jié)尾),調(diào)用方不會報錯。如果收到 GeneratorExit 異常,生成器一定不能產(chǎn)出值,否則解釋器會拋出RuntimeError 異常。生成器拋出的其他異常會向上冒泡,傳給調(diào)用方。

示例如下:

          
            from inspect import getgeneratorstate
class DemoException(Exception):
    """為這次演示定義的異常類型。"""
    pass
    
def demo_exc_handling():
    print('-> coroutine started')
    while True:
        try:
            x = yield
        except DemoException:
            print('*** DemoException handled. Continuing...')
        else:
            print('-> coroutine received: {!r}'.format(x))
    raise RuntimeError('This line should never run.')
    
>>> exc_coro = demo_exc_handling()
>>> next(exc_coro)
-> coroutine started
>>> exc_coro.send(11)
-> coroutine received: 11
>>> exc_coro.send(22)
-> coroutine received: 22

>>> exc_coro.throw(DemoException)
*** DemoException handled. Continuing...
>>> getgeneratorstate(exc_coro)
'GEN_SUSPENDED'
>>> exc_coro.close()
>>> getgeneratorstate(exc_coro)
'GEN_CLOSED'
          
        

4、讓協(xié)程返回值

在Python2中,生成器函數(shù)中的return不允許返回附帶返回值。在Python3中取消了這一限制,因而允許協(xié)程可以返回值:

          
            from collections import namedtuple
Result = namedtuple('Result', 'count average')

def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        term = yield
        if term is None:
            break
        total += term
        count += 1
        average = total/count
    return Result(count, average)
    
>>> coro_avg = averager()
>>> next(coro_avg)
>>> coro_avg.send(10)
>>> coro_avg.send(30)
>>> coro_avg.send(6.5)
>>> coro_avg.send(None)
Traceback (most recent call last):
...
StopIteration: Result(count=3, average=15.5)    
          
        

發(fā)送 None 會終止循環(huán),導(dǎo)致協(xié)程結(jié)束,返回結(jié)果。一如既往,生成器對象會拋出StopIteration 異常。異常對象的 value 屬性保存著返回的值。

注意,return 表達(dá)式的值會偷偷傳給調(diào)用方,賦值給 StopIteration 異常的一個屬性。這樣做有點(diǎn)不合常理,但是能保留生成器對象的常規(guī)行為——耗盡時拋出StopIteration 異常。如果需要接收返回值,可以這樣:

          
            >>> try:
...    coro_avg.send(None)
... except StopIteration as exc:
...    result = exc.value
...
>>> result
Result(count=3, average=15.5)
          
        

獲取協(xié)程的返回值要繞個圈子,可以使用Python3.3引入的yield from獲取返回值。yield from 結(jié)構(gòu)會在內(nèi)部自動捕獲 StopIteration 異常。這種處理方式與 for 循環(huán)處理 StopIteration 異常的方式一樣。對 yield from 結(jié)構(gòu)來說,解釋器不僅會捕獲 StopIteration 異常,還會把value 屬性的值變成 yield from 表達(dá)式的值。

5、yield from的使用

yield from 是 Python3.3 后新加的語言結(jié)構(gòu)。在其他語言中,類似的結(jié)構(gòu)使用 await 關(guān)鍵字,這個名稱好多了,因?yàn)樗鼈鬟_(dá)了至關(guān)重要的一點(diǎn):在生成器 gen 中使用 yield from subgen() 時,subgen 會獲得控制權(quán),把產(chǎn)出的值傳給 gen 的調(diào)用方,即調(diào)用方可以直接控制 subgen。與此同時,gen 會阻塞,等待 subgen 終止。

yield from 可用于簡化 for 循環(huán)中的 yield 表達(dá)式。例如:

          
            >>> def gen():
... for c in 'AB':
...     yield c
... for i in range(1, 3):
...     yield i
...
>>> list(gen())
['A', 'B', 1, 2]
          
        

可以改為

          
            >>> def gen():
...     yield from 'AB'
...     yield from range(1, 3)
...
>>> list(gen())
['A', 'B', 1, 2]
          
        

yield from x 表達(dá)式對 x 對象所做的第一件事是,調(diào)用 iter(x),從中獲取迭代器。因此,x 可以是任何可迭代的對象。

如果 yield from 結(jié)構(gòu)唯一的作用是替代產(chǎn)出值的嵌套 for 循環(huán),這個結(jié)構(gòu)很有可能不會添加到 Python 語言中。

yield from 的主要功能是打開雙向通道,把最外層的調(diào)用方與最內(nèi)層的子生成器連接起來,這樣二者可以直接發(fā)送和產(chǎn)出值,還可以直接傳入異常,而不用在位于中間的協(xié)程中添加大量處理異常的樣板代碼。有了這個結(jié)構(gòu),協(xié)程可以通過以前不可能的方式委托職責(zé)。

PEP 380 使用了一些yield from使用的專門術(shù)語:

  • 委派生成器:包含 yield from 表達(dá)式的生成器函數(shù);

  • 子生成器:從 yield from 表達(dá)式中 部分獲取的生成器;

  • 調(diào)用方:調(diào)用委派生成器的客戶端代碼;

委派生成器在 yield from 表達(dá)式處暫停時,調(diào)用方可以直接把數(shù)據(jù)發(fā)給子生成器,子生成器再把產(chǎn)出的值發(fā)給調(diào)用方。子生成器返回之后,解釋器會拋出StopIteration 異常,并把返回值附加到異常對象上,此時委派生成器會恢復(fù)。

下面是一個求平均身高和體重的示例代碼:

          
            from collections import namedtuple

Result = namedtuple('Result', 'count average')

# 子生成器
def averager():
    total = 0.0
    count = 0
    average = None
    while True:
        # main 函數(shù)發(fā)送數(shù)據(jù)到這里 
        print("in averager, before yield")
        term = yield
        if term is None: # 終止條件
            break
        total += term
        count += 1
        average = total/count

    print("in averager, return result")
    return Result(count, average) # 返回的Result 會成為grouper函數(shù)中yield from表達(dá)式的值


# 委派生成器
def grouper(results, key):
     # 這個循環(huán)每次都會新建一個averager 實(shí)例,每個實(shí)例都是作為協(xié)程使用的生成器對象
    while True:
        print("in grouper, before yield from averager, key is ", key)
        results[key] = yield from averager()
        print("in grouper, after yield from, key is ", key)


# 調(diào)用方
def main(data):
    results = {}
    for key, values in data.items():
        # group 是調(diào)用grouper函數(shù)得到的生成器對象
        group = grouper(results, key)
        print("\ncreate group: ", group)
        next(group) #預(yù)激 group 協(xié)程。
        print("pre active group ok")
        for value in values:
            # 把各個value傳給grouper 傳入的值最終到達(dá)averager函數(shù)中;
            # grouper并不知道傳入的是什么,同時grouper實(shí)例在yield from處暫停
            print("send to %r value %f now"%(group, value))
            group.send(value)
        # 把None傳入groupper,傳入的值最終到達(dá)averager函數(shù)中,導(dǎo)致當(dāng)前實(shí)例終止。然后繼續(xù)創(chuàng)建下一個實(shí)例。
        # 如果沒有g(shù)roup.send(None),那么averager子生成器永遠(yuǎn)不會終止,委派生成器也永遠(yuǎn)不會在此激活,也就不會為result[key]賦值
        print("send to %r none"%group)
        group.send(None)
    print("report result: ")
    report(results)


# 輸出報告
def report(results):
    for key, result in sorted(results.items()):
        group, unit = key.split(';')
        print('{:2} {:5} averaging {:.2f}{}'.format(result.count, group, result.average, unit))


data = {
    'girls;kg':[40, 41, 42, 43, 44, 54],
    'girls;m': [1.5, 1.6, 1.8, 1.5, 1.45, 1.6],
    'boys;kg':[50, 51, 62, 53, 54, 54],
    'boys;m': [1.6, 1.8, 1.8, 1.7, 1.55, 1.6],
}

if __name__ == '__main__':
    main(data) 
          
        

grouper 發(fā)送的每個值都會經(jīng)由 yield from 處理,通過管道傳給 averager 實(shí)例。grouper 會在 yield from 表達(dá)式處暫停,等待 averager 實(shí)例處理客戶端發(fā)來的值。averager 實(shí)例運(yùn)行完畢后,返回的值綁定到 results[key] 上。while 循環(huán)會不斷創(chuàng)建 averager 實(shí)例,處理更多的值。

外層 for 循環(huán)重新迭代時會新建一個 grouper 實(shí)例,然后綁定到 group 變量上。前一個 grouper 實(shí)例(以及它創(chuàng)建的尚未終止的 averager 子生成器實(shí)例)被垃圾回收程序回收。

代碼結(jié)果如下:

          
            create group:  
            
              
in grouper, before yield from averager, key is  girls;kg
in averager, before yield
pre active group ok
send to 
              
                 value 40.000000 now
in averager, before yield
send to 
                
                   value 41.000000 now
in averager, before yield
send to 
                  
                     value 42.000000 now
in averager, before yield
send to 
                    
                       value 43.000000 now
in averager, before yield
send to 
                      
                         value 44.000000 now
in averager, before yield
send to 
                        
                           value 54.000000 now
in averager, before yield
send to 
                          
                             none
in averager, return result
in grouper, after yield from, key is  girls;kg
in grouper, before yield from averager, key is  girls;kg
in averager, before yield

create group:  
                            
                              
in grouper, before yield from averager, key is  girls;m
in averager, before yield
pre active group ok
send to 
                              
                                 value 1.500000 now
in averager, before yield
send to 
                                
                                   value 1.600000 now
in averager, before yield
send to 
                                  
                                     value 1.800000 now
in averager, before yield
send to 
                                    
                                       value 1.500000 now
in averager, before yield
send to 
                                      
                                         value 1.450000 now
in averager, before yield
send to 
                                        
                                           value 1.600000 now
in averager, before yield
send to 
                                          
                                             none
in averager, return result
in grouper, after yield from, key is  girls;m
in grouper, before yield from averager, key is  girls;m
in averager, before yield

create group:  
                                            
                                              
in grouper, before yield from averager, key is  boys;kg
in averager, before yield
pre active group ok
send to 
                                              
                                                 value 50.000000 now
in averager, before yield
send to 
                                                
                                                   value 51.000000 now
in averager, before yield
send to 
                                                  
                                                     value 62.000000 now
in averager, before yield
send to 
                                                    
                                                       value 53.000000 now
in averager, before yield
send to 
                                                      
                                                         value 54.000000 now
in averager, before yield
send to 
                                                        
                                                           value 54.000000 now
in averager, before yield
send to 
                                                          
                                                             none
in averager, return result
in grouper, after yield from, key is  boys;kg
in grouper, before yield from averager, key is  boys;kg
in averager, before yield

create group:  
                                                            
                                                              
in grouper, before yield from averager, key is  boys;m
in averager, before yield
pre active group ok
send to 
                                                              
                                                                 value 1.600000 now
in averager, before yield
send to 
                                                                
                                                                   value 1.800000 now
in averager, before yield
send to 
                                                                  
                                                                     value 1.800000 now
in averager, before yield
send to 
                                                                    
                                                                       value 1.700000 now
in averager, before yield
send to 
                                                                      
                                                                         value 1.550000 now
in averager, before yield
send to 
                                                                        
                                                                           value 1.600000 now
in averager, before yield
send to 
                                                                          
                                                                             none
in averager, return result
in grouper, after yield from, key is  boys;m
in grouper, before yield from averager, key is  boys;m
in averager, before yield
report result: 
 6 boys  averaging 54.00kg
 6 boys  averaging 1.68m
 6 girls averaging 44.00kg
 6 girls averaging 1.58m
                                                                          
                                                                        
                                                                      
                                                                    
                                                                  
                                                                
                                                              
                                                            
                                                          
                                                        
                                                      
                                                    
                                                  
                                                
                                              
                                            
                                          
                                        
                                      
                                    
                                  
                                
                              
                            
                          
                        
                      
                    
                  
                
              
            
          
        

這個試驗(yàn)想表明的關(guān)鍵一點(diǎn)是,如果子生成器不終止,委派生成器會在yield from 表達(dá)式處永遠(yuǎn)暫停。如果是這樣,程序不會向前執(zhí)行,因?yàn)?yield from(與 yield 一樣)把控制權(quán)轉(zhuǎn)交給客戶代碼(即,委派生成器的調(diào)用方)了。

6、yield from的意義

把迭代器當(dāng)作生成器使用,相當(dāng)于把子生成器的定義體內(nèi)聯(lián)在 yield from 表達(dá)式中。此外,子生成器可以執(zhí)行 return 語句,返回一個值,而返回的值會成為 yield from 表達(dá)式的值。

PEP 380 在“Proposal”一節(jié)(https://www.python.org/dev/peps/pep-0380/#proposal)分六點(diǎn)說明了 yield from 的行為。這里幾乎原封不動地引述,不過把有歧義的“迭代器”一詞都換成了“子生成器”,還做了進(jìn)一步說明。上面的示例闡明了下述四點(diǎn):

子生成器產(chǎn)出的值都直接傳給委派生成器的調(diào)用方(即客戶端代碼);

使用 send() 方法發(fā)給委派生成器的值都直接傳給子生成器。如果發(fā)送的值是None,那么會調(diào)用子生成器的 next () 方法。如果發(fā)送的值不是 None,那么會調(diào)用子生成器的 send() 方法。如果子生成器拋出 StopIteration 異常,那么委派生成器恢復(fù)運(yùn)行。任何其他異常都會向上冒泡,傳給委派生成器;

生成器退出時,生成器(或子生成器)中的 return expr 表達(dá)式會觸發(fā)StopIteration(expr) 異常拋出;

yield from 表達(dá)式的值是子生成器終止時傳給 StopIteration 異常的第一個參數(shù)。

yield from 的具體語義很難理解,尤其是處理異常的那兩點(diǎn)。在PEP 380 中闡述了 yield from 的語義。還使用偽代碼(使用 Python 句法)演示了 yield from 的行為。

若想研究那段偽代碼,最好將其簡化,只涵蓋 yield from 最基本且最常見的用法:yield from 出現(xiàn)在委派生成器中,客戶端代碼驅(qū)動著委派生成器,而委派生成器驅(qū)動著子生成器。為了簡化涉及到的邏輯,假設(shè)客戶端沒有在委派生成器上調(diào)用throw(...) 或 close() 方法。而且假設(shè)子生成器不會拋出異常,而是一直運(yùn)行到終止,讓解釋器拋出 StopIteration 異常。上面示例中的腳本就做了這些簡化邏輯的假設(shè)。

下面的偽代碼,等效于委派生成器中的 RESULT = yield from EXPR 語句(這里針對的是最簡單的情況:不支持 .throw(...) 和 .close() 方法,而且只處理 StopIteration 異常):

          
            _i = iter(EXPR) 
try:
    _y = next(_i)
except StopIteration as _e:
    _r = _e.value
else:
    while 1:
        _s = yield _y
    try:
        _y = _i.send(_s)
    except StopIteration as _e:
        _r = _e.value
        break
RESULT = _r
          
        

但是,現(xiàn)實(shí)情況要復(fù)雜一些,因?yàn)橐幚砜蛻魧?throw(...) 和 close() 方法的調(diào)用,而這兩個方法執(zhí)行的操作必須傳入子生成器。此外,子生成器可能只是純粹的迭代器,不支持 throw(...) 和 close() 方法,因此 yield from 結(jié)構(gòu)的邏輯必須處理這種情況。如果子生成器實(shí)現(xiàn)了這兩個方法,而在子生成器內(nèi)部,這兩個方法都會觸發(fā)異常拋出,這種情況也必須由 yield from 機(jī)制處理。調(diào)用方可能會無緣無故地讓子生成器自己拋出異常,實(shí)現(xiàn) yield from 結(jié)構(gòu)時也必須處理這種情況。最后,為了優(yōu)化,如果調(diào)用方調(diào)用 next(...) 函數(shù)或 .send(None) 方法,都要轉(zhuǎn)交職責(zé),在子生成器上調(diào)用next(...) 函數(shù);僅當(dāng)調(diào)用方發(fā)送的值不是 None 時,才使用子生成器的 .send(...) 方法。

下面的偽代碼,是考慮了上述情況之后,語句:RESULT = yield from EXPR的等效代碼:

          
            _i = iter(EXPR)
try:
    _y = next(_i)
except StopIteration as _e:
    _r = _e.value
else:
    while 1:
        try:
            _s = yield _y
        except GeneratorExit as _e:
            try:
                _m = _i.close
            except AttributeError:
                pass
            else:
                _m()
            raise _e
        except BaseException as _e:
            _x = sys.exc_info()
            try:
                _m = _i.throw
            except AttributeError:
                raise _e
            else:
                try:
                    _y = _m(*_x)
                except StopIteration as _e:
                    _r = _e.value
                    break
        else:
            try:
                if _s is None:
                    _y = next(_i)
                else:
                    _y = _i.send(_s)
            except StopIteration as _e:
                _r = _e.value
                break
RESULT = _r
          
        

上面的偽代碼中,會預(yù)激子生成器。這表明,用于自動預(yù)激的裝飾器與 yield from 結(jié)構(gòu)不兼容。

三、greenlet的使用

python中為實(shí)現(xiàn)協(xié)程封裝了一些非常好用的包,首先介紹greenlet的使用。

Greenlet是python的一個C擴(kuò)展,旨在提供可自行調(diào)度的‘微線程’, 即協(xié)程。generator實(shí)現(xiàn)的協(xié)程在yield value時只能將value返回給調(diào)用者(caller)。 而在greenlet中,target.switch(value)可以切換到指定的協(xié)程(target), 然后yield value。greenlet用switch來表示協(xié)程的切換,從一個協(xié)程切換到另一個協(xié)程需要顯式指定。

以下例子:

          
            from greenlet import greenlet
def test1():
    print(12)
    gr2.switch()
    print(34)

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

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()

---------------------------

>>> 12
>>> 56
>>> 34
          
        

當(dāng)創(chuàng)建一個greenlet時,首先初始化一個空的棧, switch到這個棧的時候,會運(yùn)行在greenlet構(gòu)造時傳入的函數(shù)(首先在test1中打印 12), 如果在這個函數(shù)(test1)中switch到其他協(xié)程(到了test2 打印34),那么該協(xié)程會被掛起,等到切換回來(在test2中切換回來 打印34)。當(dāng)這個協(xié)程對應(yīng)函數(shù)執(zhí)行完畢,那么這個協(xié)程就變成dead狀態(tài)。

對于greenlet,最常用的寫法是 x = gr.switch(y)。 這句話的意思是切換到gr,傳入?yún)?shù)y。當(dāng)從其他協(xié)程(不一定是這個gr)切換回來的時候,將值付給x。

          
            import greenlet
def test1(x, y):
    z = gr2.switch(x+y)
    print 'test1 ', z

def test2(u):
    print 'test2 ', u
    gr1.switch(10)

gr1 = greenlet.greenlet(test1)
gr2 = greenlet.greenlet(test2)
print gr1.switch("hello", " world")

---------------------------

>>> 'test2 ' 'hello world'
>>> 'test1 ' 10
>>> None
          
        

上面的例子,第12行從main greenlet切換到了gr1,test1第3行切換到了gs2,然后gr1掛起,第8行從gr2切回gr1時,將值(10)返回值給了 z。

使用greenlet需要注意一下三點(diǎn):

  • 第一:greenlet創(chuàng)生之后,一定要結(jié)束,不能switch出去就不回來了,否則容易造成內(nèi)存泄露
  • 第二:python中每個線程都有自己的main greenlet及其對應(yīng)的sub-greenlet ,不能線程之間的greenlet是不能相互切換的
  • 第三:不能存在循環(huán)引用,這個是官方文檔明確說明

四、gevent的使用

gevent可以自動捕獲I/O耗時操作,來自動切換協(xié)程任務(wù)。

          
            import gevent

def f1():
    for i in range(5):
        print('run func: f1, index: %s ' % i)
        gevent.sleep(1)

def f2():
    for i in range(5):
        print('run func: f2, index: %s ' % i)
        gevent.sleep(1)

t1 = gevent.spawn(f1)
t2 = gevent.spawn(f2)
gevent.joinall([t1, t2])

------------------------------

>>> run func: f1, index: 0 
>>> run func: f2, index: 0 
>>> run func: f1, index: 1 
>>> run func: f2, index: 1 
>>> run func: f1, index: 2 
>>> run func: f2, index: 2 
>>> run func: f1, index: 3 
>>> run func: f2, index: 3 
>>> run func: f1, index: 4 
>>> run func: f2, index: 4 
          
        

由圖中可以看出,f1和f2是交叉打印信息的,因?yàn)樵诖a執(zhí)行的過程中,我們?nèi)藶槭褂胓event.sleep(0)創(chuàng)建了一個阻塞,gevent在運(yùn)行到這里時就會自動切換函數(shù)切換函數(shù)。也可以在執(zhí)行的時候sleep更長時間,可以發(fā)現(xiàn)兩個函數(shù)基本是同時運(yùn)行然后各自等待。

關(guān)于協(xié)程,首先要充分理解協(xié)程的實(shí)現(xiàn)原理,然后使用現(xiàn)有的輪子greenlet和gevent時才能更加得心應(yīng)手!


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論