C語言中實現協程案例

協程是一種用戶空間的非搶占式線程,主要用來解決等待大量的IO操作的問題。

協程vs線程

對比使用多線程來解決IO阻塞任務,使用協程的好處是不用加鎖,訪問共享的數據不用進行同步操作。這裡需要說明的一點是,使用協程之所以不需要加鎖不是因為所有的協程隻在一個線程中運行,而是因為協程的非搶占式的特點。也就是說,使用協程的話,在沒主動交出CPU之前都是不會被突然切換到其它協程上的。而線程是搶占式的,使用多線程你是不能確定你的線程什麼時候被操作系統調度,什麼時候被切換,因此需要用鎖到實現一種“原子操作”的語義。

協程vs異步回調

其實更一般更常見的做法是,使用非阻塞的IO(比如是異步IO,又或者是在syscall上自己實現的一套異步IO,如asio)並且將處理操作寫在回調函數中。這樣的做法一般沒什麼問題,但當回調函數變多,一段連貫的業務代碼就會被拆分到多個回調函數之中,增加維護的成本。因此使用協程可以用同步的寫法寫出效果相當於是異步的代碼。

利用static變量實現協程

要實現一個協程,主要的問題是如何保存函數調用的上下文。之前在網上看到一篇博客coroutines in c,用一種非常簡潔的方式實現瞭這個保存上下文的功能。實現代碼如下:

#define crBegin static int _cr_state = 0; switch(_cr_state) { case 0:
#define crReturn(x) do { _cr_state = __LINE__; return x; case __LINE__:; } while (0)
#define crFinish }

int func1() {
    crBegin
    while (1)
    {
        printf("hello world\n");
        crReturn(0);
    }
    crFinish
}

這個代碼利用瞭函數的static變量來保存函數調用狀態。註意,由於vs2013有一個調試特性,所以vs2013的__LINE__的實現不是常量因此會編譯不通過,使用gcc就可以編譯。這段代碼簡單是簡單但是有問題,比如說如果兩個協程調用同一個函數,就會出錯。因此博客裡面提及這段代碼主要是給出一個思路,如果實際使用的話這樣子肯定是不行的。

利用setjmp、longjmp實現協程

前面說過,實現協程最主要的是保存函數的調用的上下文,而這些上下文主要就兩個部分:1.各個寄存器的值,2.函數調用棧。C語言裡可以通過setjmp來保存函數調用時,各寄存器的值。保存之後,便可以通過longjmp重現回到當初setjmp的地方(可以理解成跨函數的goto)。但是,需要註意的是,setjmp僅負責保存寄存器的值,不負責維護其函數調用棧(這個看看setjmp的jmp_buf的結構就知道瞭),因此必須由使用者來手動的維護這個函數調用棧。使用setjmp、longjmp的一個常見的錯誤就是,嘗試去longjmp到一個已經執行完的函數,這時候雖然寄存器的值是當時保存的值,但是調用棧已經不是原來的調用棧瞭。

而我的做法是,在創建一個協程的時候在堆上申請一塊空間(大小為2M)作為協程的調用棧,然後在setjmp的時候,手動更改寄存器esp的值,使其指向這個我自己創建的調用棧。因此在以後運行的時候,這個協程就會使用我提供的那塊內存作為棧。

我的這個協程庫提供瞭三個接口:

  1. coro_new:創建一個協程
  2. coro_yield:將控制權返回給調度協程
  3. coro_main:運行調度協程

協程的控制流程如下:

  1. 通過coro_main運行調度協程,並找出下一個運行的協程,運行之。
  2. 運行這個協程直到其調用coro_yield將控制權返還給調度協程。
  3. 重復以上兩個步驟,直到所有協程運行完畢。

這個協程庫實現的非常簡單,隻有100來行的代碼,當然實現它的目的是為瞭提供一個最簡單的協程模型,而不是一個功能完整、魯棒性強的能投入實際業務運行的協程。

因此問題還是有很多的:

  1. 比如當在協程裡面調用棧超過2M時,這個是需要處理的,現在的代碼是沒有做的,理應中斷程序,避免寫壞堆,產生隨機的不可重現的問題。
  2. 顯然在實現時沒有考慮到多線程,如果在多線程環境裡面運行,需要代碼做同步處理。
  3. 現在的這個版本的協程有一個約定,在協程裡調用的函數不能阻塞在syscall,這顯然也是不科學的。一個完整的協程庫,應該包含一些常用的syscall的非阻塞的實現,畢竟隻有一個線程不能真的阻塞在這個調用上。

總結

當然實現協程還有比較一些更好的方法,比如如果能用glibc的ucontext庫就可以基於這個庫來實現,而不用自己手動管理函數調用的上下文瞭,如雲風實現的協程庫。

到此這篇關於C語言中實現協程案例的文章就介紹到這瞭,更多相關C語言實現協程內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: