tbox的協程實現,是stackfull模式的,需要指定獨立堆棧和協程函數,目前暫時還不能像golang那樣實現堆棧的動態增長,之後會對其進行支持。
目前提供下面一些功能特性:
1. 提供yield切換調度支持,這個是必須的哈
2. 提供suspend(掛起)/resume(恢復)協程接口,不同於yield的是,被suspend後,如果不顯示調用resume恢復它,是永遠不會被調度到的
3. 提供sleep等待接口支持
4. 提供io調度支持,支持socket等io等待(內部使用epoll, poll, kqueue, select, poll等接口調度)
5. 原生支持stream,socket,http等模塊的協程支持,可與線程進行無縫切換
6. 提供channel同道,進行協程間交互通信
7. 提供lock,semaphore等接口
這裏主要講講,基礎的切換調度如何使用。。
yield切換
這個的使用非常簡單,直接上代碼吧,嘿嘿。。
static tb_void_t switchfunc(tb_cpointer_t priv)
{
// 獲取傳入的參數
tb_size_t count = (tb_size_t)priv;
while (count--)
{
// 打印當前協程id
tb_trace_i("[coroutine: %p]: %lu", tb_coroutine_self(), count);
// 讓出當前協程,進行切換
tb_coroutine_yield();
}
}
tb_int_t main(tb_int_t argc, tb_char_t** argv)
{
// 初始化tbox
if (!tb_init(tb_null, tb_null)) return -1;
// 初始化一個調度器實例
tb_co_scheduler_ref_t scheduler = tb_co_scheduler_init();
if (scheduler)
{
// 開啓一個協程,傳遞switchfunc切換函數和參數10,最後一個參數指定棧大小,傳0使用默認值
tb_coroutine_start(scheduler, switchfunc, (tb_cpointer_t)10, 0);
// 開啓協程,使用指定的棧大小:8192
tb_coroutine_start(scheduler, switchfunc, (tb_cpointer_t)10, 8192);
/* 運行調用,開始運行裏面的所有協程
*
* 第二個參數指定是否啓用獨佔模式,這個稍後細講
*/
tb_co_scheduler_loop(scheduler, tb_true);
// 退出調度器
tb_co_scheduler_exit(scheduler);
}
// 退出tbox
tb_exit();
}
所有協程執行完後,就會從loop調用處返回,當然在協程函數內部也是可以嵌套開啓新協程的
這個時候第一個參數就不需要顯示指定scheduler了,傳tb_null
表示在當前調度器環境中開新協程,例如:
static tb_void_t switchfunc2(tb_cpointer_t priv)
{
// ..
}
static tb_void_t switchfunc(tb_cpointer_t priv)
{
// 在當前協程函數內,開啓一個新協程
tb_coroutine_start(tb_null, switchfunc2, tb_null, 0);
// 獲取傳入的參數
tb_size_t count = (tb_size_t)priv;
while (count--)
{
// 讓出當前協程,進行切換
tb_coroutine_yield();
}
}
獨佔模式
上面的代碼中提到的獨佔模式,需要特殊說明下,一般情況下,傳tb_false
到loop()中,不啓用此模式是最爲穩妥的。
因爲這樣每個scheduler對應的線程都會在Tls中維護自己的scheduler引用,使得協程中所有操作,都回去訪問當前線程tls對應的獨立scheduler,互不干涉。
這個在存在多個scheduler的情況,尤爲重要,當時這樣多少會有些tls操作上的性能損耗,像server端一般只有一個scheduler的情況下,就沒必要放到獨立tls中去了
可以傳入tb_true啓用獨佔模式,告訴tb_co_scheduler_loop(),我當前不需要tls維護,只需要一個全局scheduler變量來維護就行了
這樣的話,性能會提升一些,因此在只有一個scheduler存在的情況下,啓用獨佔效率會高些。。
sleep等待
tbox的tb_sleep
和tb_msleep()
接口,是可以原生支持協程的,在協程外就是線程等待,在協程內就是協程等待。
當然也可以直接使用協程的接口:tb_coroutine_sleep()
例如:
static tb_void_t sleepfunc(tb_cpointer_t priv)
{
while (1)
{
// 等待10ms,切換到其他協程,直到10ms後纔會切換回來繼續執行
tb_msleep(10);
}
}
resume/suspend接口
掛起域恢復,跟yield的區別就是,被suspend掛起的協程,默認是不會被切換調度回來的,除非執行resume恢復它。
因此這兩個接口是成對使用的,像sleep,lock和semaphore的內部實現,也是基於此套接口。
這兩個接口還有個功能,就是可以在兩個協程間,更加快速方便的傳遞一些參數數據,進行交互,而不需要channel支持。。
例如:
static tb_void_t resumefunc(tb_cpointer_t priv)
{
// 獲取suspend協程引用
tb_coroutine_ref_t coroutine = (tb_coroutine_ref_t)priv;
/* 恢復suspend協程,傳遞參數"hello suspend!"給suspend()作爲其返回值
*
* retval爲suspend()中傳入的參數:"hello resume!"
*/
tb_char_t const* retval = tb_coroutine_resume(coroutine, "hello suspend!");
}
static tb_void_t suspendfunc(tb_cpointer_t priv)
{
// 開啓一個恢復協程,傳入當前協程引用
tb_coroutine_start(tb_null, resumefunc, tb_coroutine_self(), 0);
/* 掛起當前協程,傳遞參數"hello resume!"給resume()作爲其返回值
*
* retval爲resume()中傳入的參數:"hello suspend!"
*/
tb_char_t const* retval = tb_coroutine_suspend("hello resume!");
}
當然,如果不需要交互數據,那麼只需要傳tb_null
就行了。。
個人主頁:TBOOX開源工程
原文出處:http://tboox.org/cn/2016/10/29/coroutine-switch/