tbox以前提供的stackfull協程庫,雖然切換效率已經很是高了,可是因爲每一個協程都須要維護一個獨立的堆棧, 內存空間利用率不是很高,在併發量很是大的時候,內存使用量會至關大。git
以前考慮過採用stacksegment方式進行內存優化,實現動態增漲,可是這樣對性能仍是有必定的影響,暫時不去考慮了。github
最近參考了下boost和protothreads的stackless協程實現,這種方式雖然易用性和靈活性上受到了不少限制,可是對切換效率和內存利用率的提高效果仍是很是明顯的。。macos
所以,我在tbox裏面也加上了對stackless協程的支持,在切換原語上參考了protothreads的實現,接口封裝上參考了boost的設計,使得更加可讀易用數據結構
先曬段實際的接口使用代碼:併發
tb_lo_coroutine_enter(coroutine) { while (1) { tb_lo_coroutine_yield(); } }
而後實測對比了下:less
* 切換性能在macosx上比tbox的stackfull版本提高了5-6倍,1000w次切換隻須要40ms * 每一個協程的內存佔用也減小到了只有固定幾十個bytes
那麼既然stackless的效率提高這麼明顯,stackfull模式還須要嗎?能夠比較下二者的優劣:socket
因爲stackless的實現比較輕量,佔用資源也不是不少,所以tbox默認放置到了micro微內核模式下,做爲基礎模塊,提供股嵌入式平臺使用函數
而通常狀況下,若是對資源使用和切換性能要求不是很是苛刻的話,使用stackfull的方式會更加方便,代碼也更易於維護oop
具體如何選擇,可根據實際使用場景,本身選擇哦。。性能
下面給的tbox的stackless協程切換實例,直觀感覺下:
static tb_void_t switchtask(tb_lo_coroutine_ref_t coroutine, tb_cpointer_t priv) { // check tb_size_t* count = (tb_size_t*)priv; // enter coroutine tb_lo_coroutine_enter(coroutine) { // loop while ((*count)--) { // trace tb_trace_i("[coroutine: %p]: %lu", tb_lo_coroutine_self(), *count); // yield tb_lo_coroutine_yield(); } } } static tb_void_t test() { // init scheduler tb_lo_scheduler_ref_t scheduler = tb_lo_scheduler_init(); if (scheduler) { // start coroutines tb_size_t counts[] = {10, 10}; tb_lo_coroutine_start(scheduler, switchtask, &counts[0], tb_null); tb_lo_coroutine_start(scheduler, switchtask, &counts[1], tb_null); // run scheduler tb_lo_scheduler_loop(scheduler); // exit scheduler tb_lo_scheduler_exit(scheduler); } }
其實總體接口使用跟tbox的那套stackfull接口相似,並無多少區別,可是相比stackfull仍是有些限制的:
1. 目前只能支持在根函數進行協程切換和等待,嵌套協程不支持 2. 協程內部局部變量使用受限
對於限制1,我正在研究中,看看有沒有好的實現方案,以前嘗試過支持下,後來發現須要按棧結構分級保存每一個入口的label地址,這樣會佔用更多內存,就放棄了。 對於限制2,因爲stackless協程函數是須要重入的,所以目前只能在enter()塊外部定以一些狀態不變的變量,enter()塊內部不要使用局部變量
接口設計上,這邊採用boost的模式:
// enter coroutine tb_lo_coroutine_enter(coroutine) { // yield tb_lo_coroutine_yield(); }
這樣比起protothreads的那種begin()和end(),更加可讀和精簡,接口也少了一個。。
tb_lo_coroutine_start
的最後兩個參數,專門用來傳遞關聯每一個協程的私有數據priv和釋放接口free,例如:
typedef struct __tb_xxxx_priv_t { tb_size_t member; tb_size_t others; }tb_xxxx_priv_t; static tb_void_t tb_xxx_free(tb_cpointer_t priv) { if (priv) tb_free(priv); } static tb_void_t test() { tb_xxxx_priv_t* priv = tb_malloc0_type(tb_xxxx_priv_t); if (priv) { priv->member = value; } tb_lo_coroutine_start(scheduler, switchtask, priv, tb_xxx_free); }
上述例子,爲協程分配一個私有的數據結構,用於數據狀態的維護,解決不能操做局部變量的問題,可是這樣寫很是繁瑣
tbox裏面提供了一些輔助接口,用來簡化這些流程:
typedef struct __tb_xxxx_priv_t { tb_size_t member; tb_size_t others; }tb_xxxx_priv_t; static tb_void_t test() { // start coroutine tb_lo_coroutine_start(scheduler, switchtask, tb_lo_coroutine_pass1(tb_xxxx_priv_t, member, value)); }
這個跟以前的代碼功能上是等價的,這裏利用tb_lo_coroutine_pass1
宏接口,自動處理了以前的那些設置流程, 用來快速關聯一個私有數據塊給新協程。
這個跟stackfull的接口用法上也是同樣的:
tb_lo_coroutine_enter(coroutine) { // 掛起當前協程 tb_lo_coroutine_suspend(); } // 恢復指定協程(這個能夠不在協程函數內部使用,其餘地方也能夠調用) tb_lo_coroutine_resume(coroutine);
掛起和恢復跟yield的區別就是,yield後的協程,以後還會被切換回來,可是被掛起的協程,除非調用resume()恢復它,不然永遠不會再被執行到。
固然通常,咱們不會直接使用suspend()和resume()接口,這兩個比較原始,若是須要定時等待,可使用:
tb_lo_coroutine_enter(coroutine) { // 等待1s tb_lo_coroutine_sleep(1000); }
來掛起當前協程1s,以後會自動恢復執行,若是要進行io等待,可使用:
static tb_void_t tb_demo_lo_coroutine_client(tb_lo_coroutine_ref_t coroutine, tb_cpointer_t priv) { // check tb_demo_lo_client_ref_t client = (tb_demo_lo_client_ref_t)priv; tb_assert(client); // enter coroutine tb_lo_coroutine_enter(coroutine) { // read data client->size = sizeof(client->data) - 1; while (client->read < client->size) { // read it client->real = tb_socket_recv(client->sock, (tb_byte_t*)client->data + client->read, client->size - client->read); // has data? if (client->real > 0) { client->read += client->real; client->wait = 0; } // no data? wait it else if (!client->real && !client->wait) { // 等待socket數據 tb_lo_coroutine_waitio(client->sock, TB_SOCKET_EVENT_RECV, TB_DEMO_TIMEOUT); // 獲取等到的io事件 client->wait = tb_lo_coroutine_events(); tb_assert_and_check_break(client->wait >= 0); } // failed or end? else break; } // trace tb_trace_i("echo: %s", client->data); // exit socket tb_socket_exit(client->sock); } }
這個跟stackfull模式除了局部變量的區別,其餘使用上幾乎同樣,也是同步模式,可是實際上tbox已經在底層把它放入了poller輪詢器中進行等待
在沒有數據,調用tb_lo_coroutine_waitio
進行socket等待事件後,tbox會自動啓用stackless調度器內部的io調度器(默認是不啓用的,延遲加載,減小無畏的資源浪費)
而後進行poll切換調度(內部根據不一樣平臺使用epoll, kqueue, poll, 後續還會支持iocp)。
若是有事件到來,會將收到事件的全部協程恢復執行,固然也能夠指定等待超時,超時返回或者強行kill中斷掉。
tbox中內置了一個stackless版本的http_server,實現也是很是輕量,經測試效率仍是很是高的, 總體表現比stackfull的實現更好。
更多stackless接口使用demo,能夠參考tbox的源碼
這個就簡單講講了,使用跟stackfull的相似,例如:
// the lock static tb_lo_lock_t g_lock; // enter coroutine tb_lo_coroutine_enter(coroutine) { // loop while (lock->count--) { // enter lock tb_lo_lock_enter(&g_lock); // trace tb_trace_i("[coroutine: %p]: enter", tb_lo_coroutine_self()); // wait some time tb_lo_coroutine_sleep(1000); // trace tb_trace_i("[coroutine: %p]: leave", tb_lo_coroutine_self()); // leave lock tb_lo_lock_leave(&g_lock); } } // init lock tb_lo_lock_init(&g_lock); // start coroutine // .. // exit lock tb_lo_lock_exit(&g_lock);
這裏只是舉個例子,實際使用中儘可能仍是別這麼直接用全局變量哦。。
我的主頁:TBOOX開源工程