嵌入式 lua 中的線程庫
無論是客戶端還是服務器,把 lua 作為嵌入語言使用的時候,都在某種程度上希望把 lua 腳本做多線程使用。也就是你的業務邏輯很可能有多條業務線索,而你希望把它們跑在同一個 lua vm 里。
lua 的 coroutine 可以很好的模擬出線程。事實上,lua 自己也把 coroutine 對象叫做 thread 類型。
最近我在反思 skynet 的 lua 封裝時,想到我們的主線程是不可以調用阻塞 api 的限制。即在主干代碼中,不可以直接 yield 。我認為可以換一種更好(而且可能更簡潔)的封裝形式來繞過這個限制,且能簡化許多其它部分的代碼。
下面介紹一下我的新想法,它不僅可以用于 skynet 也應該能推廣到一切 lua 的嵌入式應用(由你自己來編寫 host 代碼的應用,比如客戶端應用):
我們可以在第一個主模塊加載時,在 skynet 中也就是 require "skynet" 這里,啟動一個調度器的 coroutine 。
這個 coroutine 只用來管理一個數組,數組里全部是業務邏輯的線程。
這個調度器所屬的 coroutine 對應的 lua_State 指針,應該記錄在 lua vm 的注冊表中,保證只到整個 vm 關閉前都活著,那么,接下來我們就可以放心的把它保存在 host 程序中了。
這樣,在 host 程序中,除了原有的代表 vm 以及主線程的 L 之外,我們還有一個代表調度器 coroutine 的 cL 。
啟動主邏輯的地方,在創建出 vm 后,通常是加載一個主程序文本,然后使用 lua_resume 運行主線程。如果主線程正常運行結束,則之后不再利用主線程 L 做任何事情;但若主線程被掛起,則把它加到調度線程管理的線程列表組里,之后當作普通業務線程去調度。
我們可以同時提供 lua 及 C API 來向這個數組添加新線程(coroutine) ,調度器的職責是永遠在空閑的時候嘗試 resume 新添加的線程,以及在外部 IO 消息抵達時去喚醒掛起的線程。這個調度過程中,原來的主線程和其它被創建出來的線程并無區別。
而調度器本身的算法并不需要太復雜,也完全沒有必要用 host API 去實現,lua 來編寫就完全勝任了。
有了這樣的多線程機制后,我們可能還需要對 lua 原有的 coroutine 庫做一點改造。改造后可以用業務層的 coroutine 和利用 lua coroutine 實現的線程庫可以協同工作。 這個之前我已經寫過一篇 blog 了 。