首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

深入理解協程:從基本概念到現代實踐

     編輯:關於C語言

什麼是協程?

提到協程,就不得不先說說子程序——即我們常用的普通函數。調用一個函數時,它從頭開始執行,執行完畢後退出,再次調用時又會從頭開始,調用之間不保存任何狀態。而協程的關鍵在於:它可以在退出時保存狀態,下次被調用時從上一次離開的地方繼續執行。換句話說,協程之間的調用會保存上下文狀態。

協程的運作機制

如果有多個協程,它們可以相互切換執行。當一個協程主動退出時,不是用 return,而是用 yield,表示將執行權交給下一個協程,或者產出一個結果值。這點與線程有些類似:線程執行指定函數,遇到阻塞 I/O 或同步等待時會被作業系統調度到等待隊列,條件滿足後從等待點繼續。但兩者最大的區別在於調度方式:線程通常採用基於時間片的搶佔式調度,而協程採用協作式調度,由程式設計師或運行時主動讓出控制權。

從函數到協程:生成器的特例

普通函數可以輕鬆轉換為協程。協程中有一個特例叫生成器。生成器同樣能從上次返回點繼續執行,可多次 yield,也可自我掛起。但普通協程在 yield 時可指定下一個要執行的協程,生成器則不能,因此生成器常用於實現迭代器。

協程實現狀態機的優勢

協程非常適合實現狀態機。以 SMTP 客戶端為例,若用事件驅動範式,需手動記錄每步狀態,代碼極其複雜。若引入協程,協程內部流程就是自然的 SMTP 流程:每步請求發送後 yield 退出,收到回應包時事件驅動引擎再次喚醒協程,直接從上次離開處繼續,代碼變得清晰易讀。

協程與線程的性能對比

協程比線程輕量得多。線程在作業系統層面實現,調度涉及 CPU 上下文切換,開銷大;協程在語言或庫層面實現,協作式調度成本極低。高併發場景下,為每個請求分配線程效率低下,而協程輕鬆支撐大量併發。Lua 的協程需程式設計師顯式調用 yield 自行調度,較繁瑣;而 Go、Erlang 等語言將協作式調度隱藏(Go 稱為 goroutine),讓開發者專注業務邏輯。Go 中 goroutine 遇阻塞操作或 Channel 讀寫時自動讓出,完成後自動恢復,這種同步編寫異步邏輯的方式極大簡化了併發程式設計。

C 語言中的協程實現

協程多見於帶 GC 的語言,C/C++ 實現較複雜,但仍有諸多方案:如 Russ Cox 的 libtask、Boost.Coroutine 等。最引人注目的是 Simon Tatham(PuTTY 作者)基於 Duff's Device 實現的《Coroutine in C》,展示了如何用協程簡化複雜的解壓縮器。他實現了兩種協程:一種用靜態變量保存狀態,另一種通過傳遞指針的指針保存狀態,後者更通用。

Boost.Asio 中的協程

Boost.Asio 的協程實現與 Simon 的第一種方式相同。使用簡單:包含 coroutine.hpp 和 yield.hpp,創建類繼承 coroutine 或作為成員組合。Asio 作者提供了詳細指南《A potted guide to stackless coroutines》和《Composed operations, coroutines and code makeover》。

總結與建議

想使用協程,建議採用現代語言如 Go、Erlang、Scala 等,它們提供原生、高效且易用的協程支持,能讓併發程式設計變得更簡單、更安全。

常見問題補充

關於 Unity 協程的英文描述翻譯

可執行程序的協程可在任何使用 yield 聲明的地方暫停。yield 返回值指定協程何時恢復。當模型建立在多個幀之上時,協程表現優異,幾乎沒有性能開銷。無論能否正確獲取結果,StartCoroutine 總是立即返回,直到協程完成可執行程序。

Lua 中 a[n], a = a, a[n] 報錯原因

這是並行賦值。若 a 是一個表,賦值結束後 a 會變成它的元素(本例中為數字),導致後續訪問 a[n] 失敗。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved