tbox協程使用之切換與等待

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_sleeptb_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/

發佈了13 篇原創文章 · 獲贊 8 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章