--- tags: concurrency --- # [並行程式設計](https://hackmd.io/@sysprog/concurrency): Coroutine ## [coroutine](https://en.wikipedia.org/wiki/Coroutine) 並非新概念 早期電腦僅有單一 CPU (甚至沒有 microprocessor 技術),但那時的作業系統就有多任務並行的需求:當一個任務正在執行時,另一個任務到來,於是作業系統 (早期的術語稱為 monitor) 就會判斷是否切換到新的任務,若是,作業系統會保存目前的 context,然後切換到另一個任務。 [coroutine](https://en.wikipedia.org/wiki/Coroutine)於 1958 年由 Melvin Conway 提出,引述 [Wikipidia](https://en.wikipedia.org/wiki/Coroutine) 的描述: > Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed. Coroutines are well-suited for implementing familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes. 除了上述所列的實用情境外,也有被運用於 server accept 、Virtual Machine 等。而關於 preemptive multitasking ,請不要侷限於定義上,在其他實作中,coroutine 也可以藉由 [signal](https://man7.org/linux/man-pages/man7/signal.7.html) 等相關操作實作 [preemptive](https://www.ptt.cc/bbs/C_and_CPP/M.1584751926.A.0D6.html) 。 在分類上,coroutine 分成 stackful 和 stackless 兩種。一般來說 stackful 可自行切換 subroutine ,而 stackless 需要提供 `suspend` 和 `resume` 等功能自行手動切換。然而,stackful 需要有分配記憶體空間儲存 stack 等資訊,在 context switch 上會比 stackless 沒有效率(需要考量複製現存資訊、cache locality 等議題)。在 [C++20](https://en.cppreference.com/w/cpp/language/coroutines) ,C++ 提供 coroutine 的功能,而在 2014 年時,有一份關於 coroutine 新增至 C++ Standard Library 的報告,可以參考:[Stackful Coroutines and Stackless Resumable Functions](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4232.pdf); 2018 年的報告: [Working Draft, C++ Extensions for Coroutines](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4775.pdf);也可看此部落格所寫的文章:[Stackless vs. Stackful Coroutines](https://blog.varunramesh.net/posts/stackless-vs-stackful-coroutines/)。另外,其他實作案例包含,知名虛擬機器 QEMU 的 [coroutine](https://www.youtube.com/watch?v=2gNszk7OjIY) [[1]](https://www.qemu.org/docs/master/devel/block-coroutine-wrapper.html)[[2]](http://blog.vmsplice.net/2014/01/coroutines-in-qemu-basics.html) 及 Meta 的 folly 中所使用的 [coro](https://github.com/facebook/folly/tree/c2c5b28d3273a24bd4fe5393b3fb728cfe541284/folly/experimental/coro)。甚至 LLVM 或者說 Clang 編譯器也有提供 [builtin coroutine support](https://clang.llvm.org/docs/LanguageExtensions.html#c-coroutines-support-builtins) 。 以非 C/C++ 的語言來說,由 Google 開發的 [Golang](https://en.wikipedia.org/wiki/Go_(programming_language)) 也有 [Goroutine](https://go.dev/tour/concurrency/1) 。除此之外,Google 也有對 [Linux Kernel](https://lwn.net/Articles/863386/) 提出 [User-managed concurrency groups](https://lwn.net/Articles/879398/) ,相當於使用層級執行緒,該實作方式由作業系統進行 context switch 。 coroutine 當中的 context switch 實作方式,可以藉由 [setjmp](https://en.cppreference.com/w/c/program/setjmp) / [longjmp](https://man7.org/linux/man-pages/man3/longjmp.3p.html)、[組合語言](https://github.com/hnes/libaco/blob/master/acosw.S) 或 POSIX 的 [ucontext](https://pubs.opengroup.org/onlinepubs/7908799/xsh/ucontext.h.html) 實作。各有利弊需要考量運用情境,例如最簡單的 setjmp / longjmp ,可以在 cppreference 的描述可見,對於變數的型態有相關的限制: > Upon return to the scope of setjmp, all accessible objects, floating-point status flags, and other components of the abstract machine have the same values as they had when longjmp was executed, **except for the non-[volatile](https://en.cppreference.com/w/c/language/volatile) local variables** in the function containing the invocation of setjmp, whose values are indeterminate if they have been changed since the setjmp invocation. 而 [volatile](https://lwn.net/Articles/233479/) 會造成編譯器無法對其變數進行最佳化,而導致執行效率無法提昇。並且,若要在 signal handler 以 setjmp / longjmp 實作,則須考量 glibc 等函式庫所作的安全防範措施。可見 [CS170: Project 2 - User Mode Thread Library (20% of project score)](https://sites.cs.ucsb.edu/~chris/teaching/cs170/projects/proj2.html)。 延伸閱讀: [coroutine 的歷史、現在和未來](https://blog.youxu.info/2014/12/04/coroutine/) ### 為何現在仍有 coroutine 的需求? 任務可概略分為: * CPU 密集型 * I/O 密集型 早期伺服器為了解決並行需求,採用一個網路連線對應一個行程 (如 Apache HTTP Server,即 `httpd`)。後來 CPU 發展到多核,該架構調整為一個網路連線對應到一個執行緒,或者是事先配置的的 thread pool。 逐漸作業系統和應用程式框架提出 event loop 的開發介面,即 EventLoop + Callback 的方式,該模式存在多個變種,如: loop per thread,但共同特點都是 callback function 中處理事件。隨著軟體規模的擴展,callback 的變得更複雜,不乏有巢狀呼叫 (nested),這對於程式開發和除錯帶來嚴重的負面影響。 [goroutine](https://tour.golang.org/concurrency/1) 的出現帶來新的改變 —— 以同步的方式處理原本非同步執行的程式碼,於是變得更直覺、程式碼易於閱讀且除錯,適合 I/O 密集型任務,這背後的機制就是 coroutine。相較於執行緒,coroutine 佔用的記憶體空間更少,且通常不需要作業系統介入,從而在高度並行的情境可帶來優異的效能表現。 ## 案例探討 QEMU 內建 coroutine * [QEMU 中的協程: qemu-coroutine](https://royhunter.github.io/2016/06/24/qemu-coroutine/) * [include/qemu/coroutine.h](https://github.com/qemu/qemu/blob/master/include/qemu/coroutine.h) * [include/qemu/coroutine_int.h](https://github.com/qemu/qemu/blob/master/include/qemu/coroutine_int.h) * [util/qemu-coroutine.c](https://github.com/qemu/qemu/blob/master/util/qemu-coroutine.c) * [util/coroutine-sigaltstack.c](https://github.com/qemu/qemu/blob/master/util/coroutine-sigaltstack.c) * [util/coroutine-ucontext.c](https://github.com/qemu/qemu/blob/master/util/coroutine-ucontext.c) * [tests/unit/test-coroutine.c](https://github.com/qemu/qemu/blob/master/tests/unit/test-coroutine.c) LLVM 提供 coroutine 支援 [Coroutine](https://hackmd.io/@YLowy/HkT8-S20D) FreeRTOS 的 [coroutine](https://www.freertos.org/kernel/coroutines.html) - [Coroutine in Depth - Context Switch](http://jinke.me/2018-09-14-coroutine-context-switch/) - [Fibers, Oh My!](https://graphitemaster.github.io/fibers/) - [Doing multitasking the hard way](https://notnik.cc/posts/async/) ## 實作分析 * [coro](https://github.com/sysprog21/concurrent-programs/tree/master/coro) * [tinync](https://github.com/sysprog21/concurrent-programs/tree/master/tinync) * [fiber](https://github.com/sysprog21/concurrent-programs/tree/master/fiber) * [cserv](https://github.com/sysprog21/cserv) * [minicoro](https://github.com/edubart/minicoro) 延伸閱讀: [Evolution of the x86 context switch in Linux](https://www.maizure.org/projects/evolution_x86_context_switch_linux/)