起因
在第一個版本的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,不過這可能會成爲一個新的網絡庫了。