tbox新增stackless協(xié)程支持

tbox之前提供的stackfull協(xié)程庫,雖然切換效率已經非常高了依许,但是由于每個協(xié)程都需要維護一個獨立的堆棧狭园,
內存空間利用率不是很高,在并發(fā)量非常大的時候尘分,內存使用量會相當大猜惋。

之前考慮過采用stacksegment方式進行內存優(yōu)化,實現(xiàn)動態(tài)增漲音诫,但是這樣對性能還是有一定的影響惨奕,暫時不去考慮了雪位。

最近參考了下boost和protothreads的stackless協(xié)程實現(xiàn)竭钝,這種方式雖然易用性和靈活性上受到了很多限制,但是對切換效率和內存利用率的提升效果還是非常明顯的雹洗。香罐。

因此,我在tbox里面也加上了對stackless協(xié)程的支持时肿,在切換原語上參考了protothreads的實現(xiàn)庇茫,接口封裝上參考了boost的設計,使得更加可讀易用

先曬段實際的接口使用代碼:

tb_lo_coroutine_enter(coroutine)
{
    while (1)
    {
        tb_lo_coroutine_yield();
    }
}

然后實測對比了下:

* 切換性能在macosx上比tbox的stackfull版本提升了5-6倍螃成,1000w次切換只需要40ms
* 每個協(xié)程的內存占用也減少到了只有固定幾十個bytes

那么既然stackless的效率提升這么明顯旦签,stackfull模式還需要嗎?可以比較下兩者的優(yōu)劣:

  • stackfull協(xié)程:易用性和靈活性非常高寸宏,但是內存使用過大
  • stackless協(xié)程:切換效率和內存利用率很高宁炫,更加輕量,但是使用上限制較多

由于stackless的實現(xiàn)比較輕量氮凝,占用資源也不是很多羔巢,因此tbox默認放置到了micro微內核模式下,作為基礎模塊,提供股嵌入式平臺使用

而一般情況下竿秆,如果對資源使用和切換性能要求不是非称羯悖苛刻的話,使用stackfull的方式會更加方便幽钢,代碼也更易于維護

具體如何選擇歉备,可根據(jù)實際使用場景,自己選擇哦搅吁。威创。

切換

下面給的tbox的stackless協(xié)程切換實例,直觀感受下:

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接口類似谎懦,并沒有多少區(qū)別肚豺,但是相比stackfull還是有些限制的:

1. 目前只能支持在根函數(shù)進行協(xié)程切換和等待,嵌套協(xié)程不支持
2. 協(xié)程內部局部變量使用受限

對于限制1界拦,我正在研究中吸申,看看有沒有好的實現(xiàn)方案,之前嘗試過支持下享甸,后來發(fā)現(xiàn)需要按棧結構分級保存每個入口的label地址截碴,這樣會占用更多內存,就放棄了蛉威。
對于限制2日丹,由于stackless協(xié)程函數(shù)是需要重入的,因此目前只能在enter()塊外部定以一些狀態(tài)不變的變量蚯嫌,enter()塊內部不要使用局部變量

接口設計上哲虾,這邊采用boost的模式:

// enter coroutine
tb_lo_coroutine_enter(coroutine)
{
    // yield
    tb_lo_coroutine_yield();
}

這樣比起protothreads的那種begin()和end(),更加可讀和精簡择示,接口也少了一個束凑。。

參數(shù)傳遞

tb_lo_coroutine_start的最后兩個參數(shù)栅盲,專門用來傳遞關聯(lián)每個協(xié)程的私有數(shù)據(jù)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);
}

上述例子,為協(xié)程分配一個私有的數(shù)據(jù)結構谈秫,用于數(shù)據(jù)狀態(tài)的維護扒寄,解決不能操作局部變量的問題,但是這樣寫非常繁瑣

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宏接口,自動處理了之前的那些設置流程构灸,
用來快速關聯(lián)一個私有數(shù)據(jù)塊給新協(xié)程上渴。

掛起和恢復

這個跟stackfull的接口用法上也是一樣的:

tb_lo_coroutine_enter(coroutine)
{
    // 掛起當前協(xié)程
    tb_lo_coroutine_suspend();
}

// 恢復指定協(xié)程(這個可以不在協(xié)程函數(shù)內部使用岸梨,其他地方也可以調用)
tb_lo_coroutine_resume(coroutine);

掛起和恢復跟yield的區(qū)別就是,yield后的協(xié)程稠氮,之后還會被切換回來曹阔,但是被掛起的協(xié)程,除非調用resume()恢復它隔披,否則永遠不會再被執(zhí)行到赃份。

等待

當然一般,我們不會直接使用suspend()和resume()接口奢米,這兩個比較原始抓韩,如果需要定時等待,可以使用:

tb_lo_coroutine_enter(coroutine)
{
    // 等待1s
    tb_lo_coroutine_sleep(1000);
}

來掛起當前協(xié)程1s鬓长,之后會自動恢復執(zhí)行谒拴,如果要進行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數(shù)據(jù)
                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模式除了局部變量的區(qū)別涉波,其他使用上幾乎一樣英上,也是同步模式,但是實際上tbox已經在底層把它放入了poller輪詢器中進行等待

在沒有數(shù)據(jù)啤覆,調用tb_lo_coroutine_waitio進行socket等待事件后苍日,tbox會自動啟用stackless調度器內部的io調度器(默認是不啟用的,延遲加載窗声,減少無畏的資源浪費)

然后進行poll切換調度(內部根據(jù)不同平臺使用epoll, kqueue, poll, 后續(xù)還會支持iocp)相恃。

如果有事件到來,會將收到事件的所有協(xié)程恢復執(zhí)行笨觅,當然也可以指定等待超時拦耐,超時返回或者強行kill中斷掉。

tbox中內置了一個stackless版本的http_server屋摇,實現(xiàn)也是非常輕量揩魂,經測試效率還是非常高的幽邓,
整體表現(xiàn)比stackfull的實現(xiàn)更好炮温。

更多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開源工程
原文出處:http://tboox.org/cn/2016/12/03/stackless-coroutine/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市没炒,隨后出現(xiàn)的幾起案子涛癌,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拳话,死亡現(xiàn)場離奇詭異先匪,居然都是意外死亡,警方通過查閱死者的電腦和手機弃衍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門呀非,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人镜盯,你說我怎么就攤上這事岸裙。” “怎么了速缆?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵降允,是天一觀的道長。 經常有香客問我艺糜,道長拟糕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任倦踢,我火速辦了婚禮送滞,結果婚禮上,老公的妹妹穿的比我還像新娘辱挥。我一直安慰自己犁嗅,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布晤碘。 她就那樣靜靜地躺著褂微,像睡著了一般。 火紅的嫁衣襯著肌膚如雪园爷。 梳的紋絲不亂的頭發(fā)上宠蚂,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機與錄音童社,去河邊找鬼求厕。 笑死,一個胖子當著我的面吹牛扰楼,可吹牛的內容都是我干的呀癣。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼弦赖,長吁一口氣:“原來是場噩夢啊……” “哼项栏!你這毒婦竟也來了?” 一聲冷哼從身側響起蹬竖,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤沼沈,失蹤者是張志新(化名)和其女友劉穎流酬,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體列另,經...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡康吵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了访递。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晦嵌。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖拷姿,靈堂內的尸體忽然破棺而出惭载,到底是詐尸還是另有隱情,我是刑警寧澤响巢,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布描滔,位于F島的核電站,受9級特大地震影響踪古,放射性物質發(fā)生泄漏含长。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一伏穆、第九天 我趴在偏房一處隱蔽的房頂上張望拘泞。 院中可真熱鬧,春花似錦枕扫、人聲如沸陪腌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诗鸭。三九已至,卻和暖如春参滴,著一層夾襖步出監(jiān)牢的瞬間强岸,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工砾赔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蝌箍,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓过蹂,卻偏偏與公主長得像十绑,于是被迫代替她去往敵國和親聚至。 傳聞我的和親對象是個殘疾皇子酷勺,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容

  • 我是在深入學習 kotlin 時第一次看到協(xié)程,作為傳統(tǒng)線程模型的進化版扳躬,雖說協(xié)程這個概念幾十年前就有了脆诉,但是協(xié)程...
    前行的烏龜閱讀 99,760評論 32 181
  • 前言 ?今年的Google開發(fā)者大會已表明將Kotlin作為其正式的語言甚亭,現(xiàn)Google大力主推Kotlin, 在...
    Vgecanshang閱讀 3,447評論 0 15
  • 你的第一個協(xié)程 輸出結果 從本質上講击胜,協(xié)同程序是輕量級的線程亏狰。它們是與發(fā)布 協(xié)同程序構建器一起啟動的。您可以實現(xiàn)相...
    十方天儀君閱讀 2,498評論 0 2
  • go語言熱門起來之后偶摔,goroutine 和 協(xié)程的概念 也開始流行起來暇唾。云風很早的時候在自己的github上面開...
    DayDayUpppppp閱讀 1,404評論 0 2
  • 前兩天阿里巴巴開源了coobjc,沒幾天就已經2千多star了辰斋,我也看了看源碼策州,主要關注的是協(xié)程的實現(xiàn),周末折騰了...
    小涼介閱讀 20,251評論 3 25