標籤:

yield 是什麼?


這是php實現協程的方式。

要理解協程,首先要理解:代碼是代碼,函數是函數。函數包裹的代碼賦予了這段代碼附加的意義:有參數,有返回值,當函數調用另個函數的時候,必須等這個函數返回,當前函數才能返回,這就構成了後進先出,也就是stack。

而協程包裹的代碼,不是函數,不完全遵守函數的這些附加的意義,協程執行到某個點,他yield,而不是return,再次調用協程的時候,會在上次yeild的點繼續執行。

所以攜程違背了通常操作系統和x86的cpu認定的代碼執行方式,也就是stack的這種執行方式,需要運行環境(比如php,python的yield和golang的goroutine)自己調度,來實現你所要求的這種代碼執行的語義。

具體來說,一個包含yeild的php函數,就是協程,他有階段性的結算值 yield $var, 但是代碼並不返回,php的調度者接到這個值後,餵給一個generator,generator是個實現了iterator介面的+和協程通訊介面(比如send方法)的實例,所以可以用在for循環里(另個介面負責和協程通訊)。那麼gnenerator收到了這個協程的階段性的值後,他餵給for循環,等for循環下一次循環的時候,他又啟動這個協程,協程從上次中斷的點繼續執行,繼續計算,繼續yeild值給generator,generator喂for循環,繼續循環,直到協程執行完畢。


讓當前控制流在 yield 的位置停下來,然後等待外界重啟之,如果重啟了就「返回」外界送進來的參數。這個東西在「傳統的編程世界」(直覺邏輯的 ND 體系)裡面完全沒有對應物,必需硬加進來。


yield是生成器中的特有用法,而生成器是一種可以封閉整個運行狀態、可以隨時暫停繼續的模型,從傳統的程序觀點是很難描述它的,但是似乎跟函數式編程有比較密切的關係(雖然我並沒有學過函數式編程……)

設想我們有一個函數,它返回一個序列,這個序列可以是無限長的(也可以是有限長)。當然,無限長的序列我們是表示不出來的,內存會爆。但是我們通常可以把它表示成一個廣義表,它的第一項是下一個值,而第二項是剩下所有值用同樣方法形成的廣義表,也就是說我們把返回值:

a_0, a_1, a_2, a_3, ...

改寫成

(a_0, (a_1, (a_2, (a_3, ...))))

如果原始的序列是有限長的,則最終某個子表裡只有一個元素。

這樣的形式對傳統的程序來說似乎沒什麼用,但是一般我們認為廣義表是可以延遲求值的,也就是說我們可以每次取一個值,然後需要的時候再去計算下一個子表。這個模型就對應到了生成器。我們每次調用生成器讓它返回下一個值的時候,就相當於取出子表中第一項的同時,將生成器推進到了下一個子表中,這樣我們得生成器就可以返回任意有限甚至無限多個元素,而且只在需要的時候才計算出它們。

接下來我們知道,返回一個子表,和返回一個「返回子表的函數」,其實沒有什麼區別。那麼如果返回的這個函數還能接受參數呢?

f_n(x) => (a_n, f_{n+1}(x))

我們可以在獲取上一個值之後,給這個生成器傳入一個新的值,從而影響之後返回的結果,這個就是yield表達式的作用了,它返回一個值,這個值實際上是從外部傳入的,也就是我們看到的f_{n+1}輸入的參數。

但是生成器其實又跟真正的函數式編程不同,它是在傳統編程方法當中實現一個這樣功能結構的語法,真正的函數式編程自然會毫不猶豫地將它寫成尾遞歸的形式。但我們也知道,尾遞歸可以轉化成循環,那麼生成器通常就是將尾遞歸轉化成循環之後的形式,它的內部封閉了所有本來應該在遞歸中作為參數傳遞的狀態,這樣我們可以用傳統的編程的方法來寫這個生成器,這樣在許多時候是比較方便的,比如說問題異常複雜,比如說需要調用I/O等非冪等的方法的時候。

由於生成器的第二種形式可以看成每次都隱含返回一個接受參數的函數,這個函數可以代替非同步編程中的回調函數,從而用生成器編寫非同步過程,這種方法許多時候也被叫做協程,但從一開始就強調生成器的這種用法我覺得是不科學的,它其實只是用生成器代替了非同步回調函數而已,並不是自己就具有獨立執行的能力,把生成器叫做協程容易讓初學者忽略了調度器在非同步程序中的重要作用,造成誤導。


含有yield關鍵字的函數被調用時候和普通函數不同,他不執行函數代碼,而是返回一個generator對象,這個對象是可迭代對象,具有next()方法,調用一次next方法執行到yield那裡的時候暫停一下,返回一次計算的值(用法類似return),當再次調用next()方法時,接著yield下面代碼循環執行知直到又碰到yield,返回下一個循環計算的值。。。這樣不就動態生成值了么,一次一個不佔內存。碰到這個詞的時候大概就是告訴程序,來我要生成個generator對象並返回了。


切麵包,切一片吃一片切一片吃一片…


上面兩位兄台所說的yield是其作為generator的用法,也就是yield作為語句的時候。當yield當作表達式用的時候,那就是「協程」的用法了。具體請看:PHP 使用協同程序實現合作多任務


yield跟編程語言本身沒有關係。

使用return 來退出當前的執行上下文並返回一個值作為上層可以捕捉的信號。yield跟return和像,但當遇到yield的時候,它持有當前yield的狀態值並中斷執行,當再次執行的時候,接著從yield的地方執行。使用return的話,函數的控制權完全失去了,直接由函數的上層控制。


yield 在辭典里的兩個含義:1. 讓出(讓出用戶態執行),2. 產出(返回個東西),恰巧在編程語言里這兩個含義都涵蓋了~ 可以看看 『Fluent Python』相關章節,講得比較好。


直譯:讓步。沒什麼比這更好理解的方式了。


中文文檔PHP: 生成器總覽


thread等搶先式多任務,由操作系統選擇把時間片分配給哪個任務,切換動作對任務透明; 協作式多任務則必須由程序員自覺的調用掛起函數,yeild就是這個掛起操作(順便返回些值)。


php中,簡單點來說,使用yield的時候,就是把相應的值加入到了一個生成器裡面了。


在loop中使用yield就是一個生成器(generator),生成器的行為和迭代器(iterator)一樣。

迭代器實現了一個next()來返回下一個item。

而loop中使用yield,就相當於執行了next(),並且在yield i的地方返回i,等到下一個next()的時候回到yield的下一條語句繼續執行。


推薦閱讀:

目前來說在網站架構方面採用nobackend這種方案構建是否真的可行?
PHP能做什麼好玩的事?
遊戲伺服器 php框架選擇?
怎麼樣才算是精通 PHP?

TAG:PHP |