Skynet服務(wù)調(diào)度

Skynet是多線程框架,其中對(duì)應(yīng)了一些服務(wù)(Service),每個(gè)服務(wù)對(duì)應(yīng)一個(gè)Lua虛擬機(jī)嗤瞎,一個(gè)虛擬機(jī)上可以跑多個(gè)協(xié)程,但同一時(shí)刻只能有一個(gè)協(xié)程听系,每條消息處理由協(xié)程來(lái)完成贝奇,且運(yùn)行在保護(hù)模式下。Lua層實(shí)現(xiàn)的協(xié)議池和時(shí)序相關(guān)的隊(duì)列靠胜,可以類比C++協(xié)程相關(guān)實(shí)現(xiàn)掉瞳。

Skynet中的協(xié)程

Skynet本質(zhì)上只是一個(gè)消息分發(fā)器,以服務(wù)為單位并給每個(gè)服務(wù)分配一個(gè)獨(dú)立的ID浪漠,可以從任意服務(wù)向另一個(gè)服務(wù)發(fā)送消息陕习。在此基礎(chǔ)上,在服務(wù)中接入Lua虛擬機(jī)址愿,并將消息收發(fā)的API封裝成了Lua模塊该镣。

目前使用Lua編寫(xiě)的服務(wù)在最底層只有一個(gè)入口,就是接收和處理一條Skynet框架轉(zhuǎn)發(fā)過(guò)來(lái)的消息响谓∷鸷希可以通過(guò)skynet.core.callback這個(gè)內(nèi)部用C編寫(xiě)的API(通常由skynet.start調(diào)用),把一個(gè)Lua函數(shù)設(shè)置到所屬的服務(wù)模塊中娘纷。

每個(gè)模塊必須設(shè)置且只能設(shè)置一個(gè)回調(diào)函數(shù)嫁审,這個(gè)回調(diào)函數(shù)在每次收到一條消息時(shí),會(huì)接收5個(gè)參數(shù):消息類型赖晶、消息指針律适、消息長(zhǎng)度、消息Session嬉探、消息來(lái)源擦耀。

消息分為兩類:

  • 別人對(duì)你發(fā)起的請(qǐng)求
  • 你過(guò)去對(duì)外的請(qǐng)求所收到的回應(yīng)

無(wú)論是哪一類,都是通過(guò)同一個(gè)回調(diào)函數(shù)進(jìn)入涩堤。在實(shí)際使用Skynet時(shí)可以直接使用rpc的語(yǔ)法眷蜓,向外部服務(wù)發(fā)起一個(gè)遠(yuǎn)程調(diào)用,等待對(duì)方發(fā)送了回應(yīng)消息后胎围,邏輯接著向下走吁系。那么德召,框架如何把回調(diào)函數(shù)的模式轉(zhuǎn)換為阻塞API調(diào)用的形式的呢?這多虧了Lua支持協(xié)程coroutine汽纤,使一段代碼運(yùn)行了一半時(shí)掛起上岗,在之后合適的時(shí)候再繼續(xù)運(yùn)行。

為了實(shí)現(xiàn)這點(diǎn)蕴坪,需要在收到每條請(qǐng)求消息時(shí)先創(chuàng)建一個(gè)協(xié)程肴掷,在協(xié)程中去運(yùn)行該類消息的dispatch函數(shù),可使用框架中skynet.dispath函數(shù)設(shè)置消息的處理函數(shù)背传。之所以必須先創(chuàng)建協(xié)程而不能直接調(diào)用消息處理函數(shù)呆瞻,是因?yàn)闊o(wú)法預(yù)知在消息處理的過(guò)程中會(huì)不會(huì)因?yàn)樽枞鸄PI而需要掛起執(zhí)行流程。等到第一次需要掛起時(shí)才把執(zhí)行流程綁定到協(xié)程上是做不到的径玖。

接著痴脾,所有的阻塞都通過(guò)coroutine.yield函數(shù)掛起當(dāng)前協(xié)程,并把掛起類型以及可能用到的數(shù)據(jù)傳出來(lái)梳星≡蘩担框架會(huì)捕獲這些參數(shù)也就進(jìn)一步知道去做什么,也也就解釋了阻塞API為什么必須在消息處理函數(shù)中調(diào)用冤灾,而不能直接卸載服務(wù)的主體代碼中的原因前域。因?yàn)槌跏蓟糠值拇a并不運(yùn)行在框架創(chuàng)建出來(lái)的協(xié)程中。

例如:對(duì)于skynet.call其實(shí)是生成一個(gè)對(duì)當(dāng)前服務(wù)來(lái)說(shuō)唯一的session號(hào)瞳购,調(diào)用yield給框架發(fā)送CALL指令话侄】魍疲框架中的resume捕獲到CALL之后学赛,會(huì)把Session和Coroutine對(duì)象記錄在表中,然后掛起協(xié)程吞杭,并結(jié)束當(dāng)前的回調(diào)函數(shù)盏浇。等待Skynet底層框架后續(xù)消息進(jìn)來(lái)時(shí)再處理。實(shí)際上芽狗,這里還會(huì)處理skynet.fork創(chuàng)建的額外線程绢掰。

服務(wù)調(diào)度API

local skynet = require "skynet"
skynet.sleep(time)

設(shè)置當(dāng)前任務(wù)休眠等待的微秒數(shù)

skynet.fork(func, ...)

fork用于創(chuàng)建并啟動(dòng)新任務(wù)

fork用于啟動(dòng)一個(gè)新的任務(wù)去執(zhí)行函數(shù)func,實(shí)質(zhì)上它是開(kāi)了一個(gè)協(xié)程童擎,函數(shù)調(diào)用完成后會(huì)返回線程句柄滴劲。雖然可以使用原生的coroutine.create來(lái)創(chuàng)建協(xié)程,但這樣做會(huì)打亂Skynet的工作流程顾复。

skynet.yield()

yield讓出當(dāng)前任務(wù)執(zhí)行流程

yield會(huì)讓出當(dāng)前任務(wù)執(zhí)行流程班挖,使本服務(wù)內(nèi)其它任務(wù)有機(jī)會(huì)執(zhí)行,隨后會(huì)繼續(xù)運(yùn)行芯砸。

skynet.wait()

wait讓出當(dāng)前任務(wù)執(zhí)行流程直到使用wakeup喚醒它

skynet.wakeup(co)

wakeup用于喚醒使用waitsleep后處于等待狀態(tài)的任務(wù)

skynet.timeout(time, func)

timeout用于設(shè)定一個(gè)定時(shí)觸發(fā)函數(shù)func萧芙,在time * 0.01s后觸發(fā)给梅。

skynet.starttime()

starttime用于返回當(dāng)前進(jìn)程的啟動(dòng)UTC時(shí)間(秒)

skynet.now()

now用于返回當(dāng)前進(jìn)程啟動(dòng)后經(jīng)過(guò)的時(shí)間(微秒)

skynet.time()

time用于通過(guò)starttimenow計(jì)算出當(dāng)前UTC時(shí)間秒數(shù)

sleep 休眠 定時(shí)器

skynet.sleep(ti)函數(shù)是將當(dāng)前協(xié)程掛起ti個(gè)單位時(shí)間,一個(gè)單位時(shí)間是1/100秒双揪。sleep向框架注冊(cè)注冊(cè)了一個(gè)定時(shí)器的實(shí)現(xiàn)动羽,框架會(huì)在ti時(shí)間后發(fā)送一個(gè)定時(shí)器消息來(lái)喚醒這個(gè)協(xié)程。sleep函數(shù)是一個(gè)阻塞API渔期,返回值nil會(huì)告訴你時(shí)間到了运吓,返回值BREAK則表示被skynet.wakeup給喚醒了。

$ cd skynet
$ vim demo/service_sleep.lua
local skynet = require "skynet"

skynet.start(function()
  skynet.error("sleep begin")
  skynet.sleep(300)
  skynet.error("sleep end")
end)

$ cp example/config demo/config
$ cp example/config.path demo/config.path
$ vim demo/config.path
# 將example替換為demo
$ vim demo/config
# 將main替換為service_sleep
$ ./skynet demo/config
[:01000009] sleep begin 
service_sleep # 手工輸入
[:01000009] sleep end
[:01000002] KILL self

注意:在console服務(wù)中輸入service_sleep后會(huì)發(fā)現(xiàn)疯趟,新服務(wù)不會(huì)立即啟動(dòng)羽德,因?yàn)?code>console服務(wù)正忙于第一個(gè)服務(wù)的初始化,需要等待3秒后新服務(wù)才會(huì)被console處理迅办。這種做法實(shí)際上是錯(cuò)誤的宅静,在skynet.start中服務(wù)初始化中是不允許有阻塞的存在,服務(wù)初始化要求盡量快的執(zhí)行完成站欺,所以業(yè)務(wù)邏輯代碼一般不應(yīng)該寫(xiě)在skynet.start函數(shù)中姨夹。

fork 在服務(wù)中開(kāi)啟新線程

在Skynet的服務(wù)中,可以開(kāi)啟一個(gè)新的線程用來(lái)處理業(yè)務(wù)矾策,注意這里的線程并非傳統(tǒng)意義上的線程磷账,而更像是虛擬線程,其實(shí)是通過(guò)協(xié)程來(lái)模擬的贾虽。

在Skynet中所有的Lua層函數(shù)都是以協(xié)程的方式被執(zhí)行的逃糟,包括skynet.fork產(chǎn)生的函數(shù)。除非在skynet.start之外調(diào)用函數(shù)蓬豁,由于start函數(shù)調(diào)用timeout產(chǎn)生協(xié)程绰咽,而fork則產(chǎn)生的是協(xié)程列表

Lua層設(shè)置的回調(diào)函數(shù)skynet.dispatch_message主要調(diào)用了raw_dispatch_message,這里才是驅(qū)動(dòng)協(xié)程函數(shù)執(zhí)行的位置地粪。一個(gè)協(xié)程結(jié)束或掛起后將由suspend函數(shù)來(lái)接管取募。

如果入口函數(shù)start中沒(méi)有調(diào)用forksleep蟆技、wait之類的函數(shù)玩敏,那么驅(qū)動(dòng)start執(zhí)行的消息將結(jié)束。

$ vim demo/service_fork.lua
local skynet = require "skynet"

function task(timeout)
  skynet.error("coroutine fork: ", coroutine.running())
  skynet.error("sleep begin ", timeout)
  skynet.sleep(timeout)
  skynet.error("sleep end")
end

skynet.start(function()
  skynet.error("coroutine start: ", coroutine.running())
  -- 開(kāi)啟新線程來(lái)執(zhí)行task任務(wù)
  skynet.fork(task, 500)
end)

$ vim demo/config
start = "service_fork"

$ ./skynet demo/service_fork
[:01000009] coroutine start: thread: 0x7f173b4f1068 false
[:01000009] coroutine fork: thread: 0x7f173b4f1148 false
[:01000009] sleep begin  500
[:01000002] KILL self
[:01000009] sleep end

注意:可以看到當(dāng)service_fork啟動(dòng)后质礼,console服務(wù)仍然可以接收終端輸入的服務(wù)并啟動(dòng)旺聚。若是需要長(zhǎng)時(shí)間運(yùn)行并且出現(xiàn)阻塞的情況,可使用skynet.fork創(chuàng)建新的協(xié)程來(lái)運(yùn)行眶蕉。

查看skynet/lualib/skynet.lua文件可知道skynet.fork()函數(shù)其實(shí)是使用的coroutine.create()函數(shù)來(lái)實(shí)現(xiàn)的砰粹。

$ vim lublib/skynet.lua
local coroutine_pool = setmetable({}, {__mode = "kv"})

local function co_create(f)
  local co = table.remove(coroutine_pool)
  if co==nil then
    co = coroutine.create(function(...)
      -- 函數(shù)執(zhí)行完畢
      f(...) 
      while true do
        f = nil
        coroutine_pool[#coroutine_pool + 1] = co
        -- 協(xié)程掛起,將由suspend函數(shù)接管妻坝,執(zhí)行cmd=="TEXT"分支
        f = coroutine_yield "EXIT" 
        f(coroutine_yield())
      end
    end)
  else
    -- 回到前一次消息掛起的位置返回的f就是要執(zhí)行的
    coroutine_resume(co, f)
  end
  return co
end

每次使用skynet.fork()其實(shí)都是從協(xié)程池中獲取未被使用的協(xié)程伸眶,并把該協(xié)程加入到fork隊(duì)列中惊窖,等待一個(gè)消息調(diào)度,然會(huì)會(huì)一次把fork隊(duì)列中的協(xié)程拿出來(lái)執(zhí)行一遍厘贼。執(zhí)行結(jié)束后會(huì)把協(xié)程重新丟入?yún)f(xié)程池中界酒,這樣可避免重復(fù)開(kāi)啟和關(guān)閉協(xié)程帶來(lái)的額外開(kāi)銷。

案例:長(zhǎng)時(shí)間占用執(zhí)行權(quán)限的任務(wù)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末嘴秸,一起剝皮案震驚了整個(gè)濱河市毁欣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌岳掐,老刑警劉巖凭疮,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異串述,居然都是意外死亡执解,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門纲酗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)衰腌,“玉大人,你說(shuō)我怎么就攤上這事觅赊∮胰铮” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵吮螺,是天一觀的道長(zhǎng)饶囚。 經(jīng)常有香客問(wèn)我,道長(zhǎng)鸠补,這世上最難降的妖魔是什么萝风? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮莫鸭,結(jié)果婚禮上闹丐,老公的妹妹穿的比我還像新娘。我一直安慰自己被因,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布衫仑。 她就那樣靜靜地躺著梨与,像睡著了一般。 火紅的嫁衣襯著肌膚如雪文狱。 梳的紋絲不亂的頭發(fā)上粥鞋,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音瞄崇,去河邊找鬼呻粹。 笑死壕曼,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的等浊。 我是一名探鬼主播腮郊,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼筹燕!你這毒婦竟也來(lái)了轧飞?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤撒踪,失蹤者是張志新(化名)和其女友劉穎过咬,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體制妄,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掸绞,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了耕捞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片集漾。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖砸脊,靈堂內(nèi)的尸體忽然破棺而出具篇,到底是詐尸還是另有隱情,我是刑警寧澤凌埂,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布驱显,位于F島的核電站,受9級(jí)特大地震影響瞳抓,放射性物質(zhì)發(fā)生泄漏埃疫。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一孩哑、第九天 我趴在偏房一處隱蔽的房頂上張望栓霜。 院中可真熱鬧,春花似錦横蜒、人聲如沸胳蛮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)仅炊。三九已至,卻和暖如春澎蛛,著一層夾襖步出監(jiān)牢的瞬間抚垄,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呆馁,地道東北人桐经。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像浙滤,于是被迫代替她去往敵國(guó)和親阴挣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容