協(xié)程
簡單的對比和示例
由于協(xié)程不如子例程那樣被普遍所知,最好對它們作個比較。
子例程的起始處是惟一的入口點,一旦退出即完成了子例程的執(zhí)行,子例程的一個實例只會返回一次。
協(xié)程可以通過yield來調用其它協(xié)程。通過yield方式轉移執(zhí)行權的協(xié)程之間不是調用者與被調用者的關系,而是彼此對稱、平等的。
協(xié)程的起始處是第一個入口點,在協(xié)程里,返回點之后是接下來的入口點。子例程的生命期遵循后進先出(最后一個被調用的子例程最先返回);相反,協(xié)程的生命期完全由他們的使用的需要決定。
這里是一個簡單的例子證明協(xié)程的實用性。假設你有一個生產者-消費者的關系,這里一個協(xié)程生產產品并將它們加入隊列,另一個協(xié)程從隊列中取出產品并使用它。為了提高效率,你想一次增加或刪除多個產品。代碼可能是這樣的:
var q := new queue
生產者協(xié)程
消費者協(xié)程
每個協(xié)程在用yield命令向另一個協(xié)程交出控制時都盡可能做了更多的工作。放棄控制使得另一個例程從這個例程停止的地方開始,但因為現(xiàn)在隊列被修改了所以他可以做更多事情。盡管這個例子常用來介紹多線程,實際沒有必要用多線程實現(xiàn)這種動態(tài):yield語句可以通過由一個協(xié)程向另一個協(xié)程直接分支的方式實現(xiàn)。
詳細比較
因為相對于子例程,協(xié)程可以有多個入口和出口點,可以用協(xié)程來實現(xiàn)任何的子例程。事實上,正如Knuth所說:“子例程是協(xié)程的特例?!?/span>
每當子例程被調用時,執(zhí)行從被調用子例程的起始處開始;然而,接下來的每次協(xié)程被調用時,從協(xié)程返回(或yield)的位置接著執(zhí)行。
因為子例程只返回一次,要返回多個值就要通過集合的形式。這在有些語言,如Forth里很方便;而其他語言,如C,只允許單一的返回值,所以就需要引用一個集合。相反地,因為協(xié)程可以返回多次,返回多個值只需要在后繼的協(xié)程調用中返回附加的值即可。在后繼調用中返回附加值的協(xié)程常被稱為產生器。
子例程容易實現(xiàn)于堆棧之上,因為子例程將調用的其他子例程作為下級。相反地,協(xié)程對等地調用其他協(xié)程,最好的實現(xiàn)是用continuations(由有垃圾回收的堆實現(xiàn))以跟蹤控制流程。
協(xié)程之常見用例
協(xié)程有助于實現(xiàn):
狀態(tài)機:在一個子例程里實現(xiàn)狀態(tài)機,這里狀態(tài)由該過程當前的出口/入口點確定;這可以產生可讀性更高的代碼。
角色模型:并行的角色模型,例如計算機游戲。每個角色有自己的過程(這又在邏輯上分離了代碼),但他們自愿地向順序執(zhí)行各角色過程的中央調度器交出控制(這是合作式多任務的一種形式)。
產生器:它有助于輸入/輸出和對數(shù)據(jù)結構的通用遍歷。
支持協(xié)程的編程語言
Simula
Modula-2
C#
Stackless Python
Lua
Io
Go
JavaScript(ECMA-262 6th Edition)
由于continuations被用來實現(xiàn)協(xié)程,支持continuations的編程語言也非常容易就支持協(xié)程。
協(xié)程的替代者和實現(xiàn)
到2003年,很多最流行的編程語言,包括C和他的后繼,都未在語言內或其標準庫中直接支持協(xié)程。(這在很大程度上是受基于堆棧的子例程實現(xiàn)的限制)。
有些情況下,使用協(xié)程的實現(xiàn)策略顯得很自然,但是此環(huán)境下卻不能使用協(xié)程。典型的解決方法是創(chuàng)建一個子例程,它用布爾標志的集合以及其他狀態(tài)變量在調用之間維護內部狀態(tài)。代碼中基于這些狀態(tài)變量的值的條件語句產生出不同的執(zhí)行路徑及后繼的函數(shù)調用。另一種典型的解決方案是用一個龐大而復雜的switch語句實現(xiàn)一個顯式狀態(tài)機。這種實現(xiàn)理解和維護起來都很困難。
在當今的主流編程環(huán)境里,線程是協(xié)程的合適的替代者,線程提供了用來管理“同時”執(zhí)行的代碼段實時交互的功能。因為要解決大量困難的問題,線程包括了許多強大和復雜的功能并導致了困難的學習曲線。當需要的只是一個協(xié)程時,使用線程就過于技巧了。然而——不像其他的替代者——在支持C的環(huán)境中,線程也是廣泛有效的,對很多程序員也比較熟悉,并被很好地實現(xiàn),文檔化和支持。在POSIX里有一個標準的良定義的線程實現(xiàn)pthread.
用C的實現(xiàn)
C標準庫里的函數(shù)setjmp和longjmp可以用來實現(xiàn)一種協(xié)程。不幸的是,正如harbison and Steele所述,“setjmp和longjmp的相當?shù)仉y以實現(xiàn),程序員要對使用它作最少的假設。”這意味著如果沒有留意Harbison和Steele的警告而在某個環(huán)境下使用了setjmp和longjmp,在其他環(huán)境下可能不能正常工作。更糟糕的是,錯誤的實現(xiàn)并非個例。
人們作了大量的嘗試,在C里用子例程和宏實現(xiàn)協(xié)程,這些嘗試有不同程度的成功之處。Simon Tatham的貢獻(見下文)是這一方法的很好示例。他自己注解是對這一方法的限制做了很好的評價。這種方法的確可以提高代碼段的可寫性,可讀性,可維護性還是存在爭議的。用Titham的話說:“當然,這一技巧破壞了這本書的每一個編碼標準……[但是]任何試圖犧牲算法明晰來確保語法清晰的編碼標準都應該被重寫。如果你的老板因為因為你使用了這些技巧而解雇你,在保安把你從大樓里拖出來的同時不斷地告訴他們上面那句話。
著名的實現(xiàn):
[1]- Simon Tatham用C實現(xiàn)的協(xié)程
Portable Coroutine Library- C library usingPOSIX/SUSv3 facilities.
Python 實現(xiàn)
PEP 342- planned support for coroutines based on generators
greenlets
gevent
Perl 實現(xiàn)
Coro- Coro是Perl5中的一種協(xié)程實現(xiàn),它使用C作為底層,所以具有良好的執(zhí)行性能,而且可以配合AnyEvent共同使用,極大的彌補了Perl在線程上劣勢
Tcl 實現(xiàn)
從 Tcl 8.6 開始,Tcl 核心內置協(xié)程支持,成為了繼事件循環(huán)、線程后的另一種內置的強大功能。
參考
高德納. Fundamental Algorithms, Third Edition. Addison-Wesley, 1997. ISBN 0-201-89683-4. Section 1.4.2: Coroutines, pp.193–200.
C: A Reference Manual. Samuel P. Harbison and Guy L. Steele, Jr. Third edition; Prentice-Hall, 1991, ISBN 0-13-110933-2.
另見
多任務處理
迭代器
Generators
惰性求值
管道
Protothreads
子程序
免責聲明:以上內容版權歸原作者所有,如有侵犯您的原創(chuàng)版權請告知,我們將盡快刪除相關內容。感謝每一位辛勤著寫的作者,感謝每一位的分享。
相關資料
- 有價值
- 一般般
- 沒價值
{{item.userName}} 舉報
{{item.time}} {{item.replyListShow ? '收起' : '展開'}}評論 {{curReplyId == item.id ? '取消回復' : '回復'}}
{{_reply.userName}} 舉報
{{_reply.time}}