libcoro:在c++中支持coroutine

起因

在第一個版本的libtnet開發完成之後,我一直在思考如何讓異步方式的網絡編程更加簡單。

雖然libtnet通過c++ shared_ptr以及function等技術很大程度上面解決了異步代碼編寫的一些問題,但是仍然會出現代碼邏輯被強制拆分的情況。而這個則是項目中童鞋無法很好的使用其進行開發的原因。

所以我考慮讓libtnet支持coroutine。

Coroutine

第一次接觸coroutine的概念是在lua裏面,記得當時想了很久纔算弄明白了coroutine的使用以及原理。在lua中,coroutine的使用如下:

co = coroutine.create(function ()
        print("begin yield")
        coroutine.yield()
        print("after yield")
    end)

coroutine.resume(co)
print("after resume")

coroutine.resume(co)

我們可以通過resume執行一個新創建或者已經被掛起的coroutine,通過yield掛起當前的coroutine,這樣就可以實現類似多線程方式下面的多任務調度。

至於coroutine的原理,很多地方都有說明,主要就在於每個coroutine都有自己的堆棧,這樣當coroutine掛起的時候,它的當前執行狀態會被完整保留,下次resume的時候就可以接着執行了。

而使用coroutine的好處,我覺得最大的一點在於它將拆分的異步邏輯同步化了,更利於代碼編寫。

在使用python tornado的時候,我們開始階段寫了太多的callback回調,以至於代碼的維護非常困難,而這個則在引入greenlet後有了明顯好轉。

而後續在使用go語言中,因爲它原生的支持coroutine(其實在go裏面更準確的說法應該是goroutine),寫代碼非常的方便,所以現在go已經成爲了我服務器的首選開發語言,我也用它開發了多個項目(如mixer,一個mysql proxy),並且已經在公司項目中實施。

當然,使用coroutine並不是毫無缺點的:

  • 每個coroutine都需要維護自己的堆棧,當我們需要創建數以百萬計的coroutine的時候,內存的開銷就需要考慮了。
  • coroutine的切換,都需要保留當前的上下文環境,以便於下次resume的時候接着執行,如果coroutine切換頻繁,開銷也不小。

libcoro

很早之前使用luajit的時候,我就知道可以在c++中實現coroutine的功能,在linux中,這通過makecontext,swapcontext等相關函數實現。雖然也可以通過setjmp/longjmp這兩個古老的函數實現,但看了luajit的coco就知道,即使在linux下面,它也需要寫很多define宏去適配。

所以,我只考慮使用makecontext這套函數族來實現coroutine。雖然swapcontext會有性能問題,詳見這裏,但早期我還不打算對其進行性能優化。

libcoro是一個簡單的c++ coroutine庫,只支持linux(因爲我們的服務器只有linux的)。

在接口上面,libcoro參考的是lua的coroutine的接口設計,使用非常簡單:

void func1()
{
    coroutine.yield();
}

void func2(Coro_t co1)
{
    coroutine.resume(co1);    
    coroutine.yield();
}

void func()
{
    Coro_t co1 = coroutine.create(std::bind(&func1));    
    coroutine.resume(co1);    
    Coro_t co2 = coroutine.create(std::bind(&func2, co1));
    coroutine.resume(co2);
    coroutine.resume(co2);
}

int main()
{    
    Coro_t co = coroutine.create(std::bind(&func));
    coroutine.resume(co);
    return 0;
}
  • coroutine.create創建一個coroutine,參數爲一個std::function,這樣我們就可以通過std::bind非常方便的實現函數閉包了。
  • coroutine.resume喚醒一個掛起或者新建的coroutine。
  • coroutine.yield掛起當前coroutine。
  • coroutine.running獲取當前運行的coroutine,如果是主線程調用,則返回0。
  • coroutine.status獲取coroutine的狀態。

後續我考慮將libtnet支持coroutine,不過這可能會成爲一個新的網絡庫了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章