skynet.fork(func, …) 的使用
大家先看下 fork的代码:(skynet.lua)
function skynet.fork(func,...)
local n = select("#", ...)
local co
if n == 0 then
co = co_create(func)
else
local args = { ... }
co = co_create(function() func(table.unpack(args,1,n)) end)
end
tinsert(fork_queue, co)
return co
end
skynet.fork() 中的代码逻辑很简单,
- select() 检查函数参数传入数量
- 通过调用 co_create() 创建携程,如果有参数传入,需要在创建携程的时候将参数传递过去。
- 将co_create()返回的参数,插入至 fork_queue 中。
下面在看下 co_create() 的实现逻辑:
local coroutine_pool = setmetatable({}, { __mode = "kv" })
local function co_create(f)
local co = tremove(coroutine_pool)
if co == nil then
co = coroutine_create(function(...)
f(...)
while true do
local session = session_coroutine_id[co]
if session and session ~= 0 then
local source = debug.getinfo(f,"S")
skynet.error(string.format("Maybe forgot response session %s from %s : %s:%d",
session,
skynet.address(session_coroutine_address[co]),
source.source, source.linedefined))
end
-- coroutine exit
local tag = session_coroutine_tracetag[co]
if tag ~= nil then
if tag then c.trace(tag, "end") end
session_coroutine_tracetag[co] = nil
end
local address = session_coroutine_address[co]
if address then
session_coroutine_id[co] = nil
session_coroutine_address[co] = nil
end
-- recycle co into pool
f = nil
coroutine_pool[#coroutine_pool+1] = co
-- recv new main function f
f = coroutine_yield "SUSPEND"
f(coroutine_yield())
end
end)
else
-- pass the main function f to coroutine, and restore running thread
local running = running_thread
coroutine_resume(co, f)
running_thread = running
end
return co
end
改段内容比较长,主体分为3个部分:
1. 在 coroutine_pool 取出一个携程,并赋值给 co
2. 携程池中没有携程,co == nil,调用 coroutine_create 创建一个新的携程,并传递一个函数做为参数。改函数中,第一步先调 co_create 做为参数传递的函数 f;在函数执行完成后,会进入一个 while(true) 的死循环
3. 携程池中有携程,创建新的携程。
在携程创建好后,函数在什么时候去调用这些携程呢,
function skynet.dispatch_message(...)
local succ, err = pcall(raw_dispatch_message,...)
while true do
local co = tremove(fork_queue,1)
if co == nil then
break
end
local fork_succ, fork_err = pcall(suspend,co,coroutine_resume(co))
if not fork_succ then
if succ then
succ = false
err = tostring(fork_err)
else
err = tostring(err) .. "\n" .. tostring(fork_err)
end
end
end
assert(succ, tostring(err))
end
在服务器处理消息的时候,会将 fork_queue 的携程全部一次执行一遍。实际执行函数 suspend()
-- suspend is local function
function suspend(co, result, command)
if not result then
local session = session_coroutine_id[co]
if session then -- coroutine may fork by others (session is nil)
local addr = session_coroutine_address[co]
if session ~= 0 then
-- only call response error
local tag = session_coroutine_tracetag[co]
if tag then c.trace(tag, "error") end
c.send(addr, skynet.PTYPE_ERROR, session, "")
end
session_coroutine_id[co] = nil
end
session_coroutine_address[co] = nil
session_coroutine_tracetag[co] = nil
skynet.fork(function() end) -- trigger command "SUSPEND"
error(traceback(co,tostring(command)))
end
if command == "SUSPEND" then
dispatch_wakeup()
dispatch_error_queue()
elseif command == "QUIT" then
-- service exit
return
elseif command == "USER" then
-- See skynet.coutine for detail
error("Call skynet.coroutine.yield out of skynet.coroutine.resume\n" .. traceback(co))
elseif command == nil then
-- debug trace
return
else
error("Unknown command : " .. command .. "\n" .. traceback(co))
end
end