lua服务执行过程中协程的挂起和重新唤醒
2018-01-01 10:59
288 查看
lua服务在执行回调函数的过程中,调用某些函数会挂起协程,比如skynet.call, skynet.ret, skynet.response等等,这些函数把协程挂起后,如何唤醒呢?
本文将对所有调用
功能:发起了一次 RPC ,并阻塞等待回应。
唤醒方式:目标服务调用skynet.ret, 返回一个PTYPE_RESPONSE类型的消息,在raw_dispatch_message函数内,会专门对这类消息做特殊处理,从之前缓存的表(key是session)中取出co, 然后再resume。
详细过程可以参考这篇文章:skynet.call流程
它和 skynet.call 功能类似(也是阻塞 API)。但发送时不经过 pack 打包流程,收到回应后,也不走 unpack 流程。
所以其唤醒方式同skynet.call。
此函数的wiki文档:
回应一个消息可以使用
由于 skynet 中最常用的消息类别是 lua ,这种消息是经过 skynet.pack 打包的,所以惯用法是
skynet.ret 在同一个消息处理的 coroutine 中只可以被调用一次,多次调用会触发异常。有时候,你需要挂起一个请求,等将来时机满足,再回应它。而回应的时候已经在别的 coroutine 中了。针对这种情况,你可以调用 skynet.response(skynet.pack) 获得一个闭包,以后调用这个闭包即可把回应消息发回。这里的参数 skynet.pack 是可选的,你可以传入其它打包函数,默认即是 skynet.pack 。
关键代码
可以看出,这个函数在调用yield后,suspend函数会马上调用resume唤醒它,所以此函数是非阻塞 API 。
使用心得:
一般的使用习惯是把skynet.ret作为回调函数的最后一句,在这句执行完后,整个回调函数就结束了,其协程也将被回收。
类似这样:
这个函数的唤醒方式同ret一样,也是在suspend函数内重新唤醒。
同样也是非阻塞API。
关于此函数更详细的分析,请参考这篇文章:skynet.response分析
本文将对所有调用
coroutine.yield的API的唤醒做下分析。(比较拗口,找不到更好的表达方式了)
skynet.call
function skynet.call(addr, typename, ...) local p = proto[typename] local session = c.send(addr, p.id , nil , p.pack(...)) if session == nil then error("call to invalid address " .. skynet.address(addr)) end return p.unpack(yield_call(addr, session)) end
功能:发起了一次 RPC ,并阻塞等待回应。
唤醒方式:目标服务调用skynet.ret, 返回一个PTYPE_RESPONSE类型的消息,在raw_dispatch_message函数内,会专门对这类消息做特殊处理,从之前缓存的表(key是session)中取出co, 然后再resume。
详细过程可以参考这篇文章:skynet.call流程
skynet.rawcall
function skynet.rawcall(addr, typename, msg, sz) local p = proto[typename] local session = assert(c.send(addr, p.id , nil , msg, sz), "call to invalid address") return yield_call(addr, session) end
它和 skynet.call 功能类似(也是阻塞 API)。但发送时不经过 pack 打包流程,收到回应后,也不走 unpack 流程。
所以其唤醒方式同skynet.call。
skynet.ret
function skynet.ret(msg, sz) msg = msg or "" return coroutine_yield("RETURN", msg, sz) end
此函数的wiki文档:
回应一个消息可以使用
skynet.ret(message, size)。它会将 message size 对应的消息附上当前消息的 session ,以及
skynet.PTYPE_RESPONSE这个类别,发送给当前消息的来源 source 。由于某些历史原因(早期的 skynet 默认消息类别是文本,而没有经过特殊编码),这个 API 被设计成传递一个 C 指针和长度,而不是经过当前消息的 pack 函数打包。或者你也可以省略 size 而传入一个字符串。
由于 skynet 中最常用的消息类别是 lua ,这种消息是经过 skynet.pack 打包的,所以惯用法是
skynet.ret(skynet.pack(...))。btw,skynet.pack(…) 返回一个 lightuserdata 和一个长度,符合 skynet.ret 的参数需求;与之对应的是 skynet.unpack(message, size) 它可以把一个 C 指针加长度的消息解码成一组 Lua 对象。
skynet.ret 在同一个消息处理的 coroutine 中只可以被调用一次,多次调用会触发异常。有时候,你需要挂起一个请求,等将来时机满足,再回应它。而回应的时候已经在别的 coroutine 中了。针对这种情况,你可以调用 skynet.response(skynet.pack) 获得一个闭包,以后调用这个闭包即可把回应消息发回。这里的参数 skynet.pack 是可选的,你可以传入其它打包函数,默认即是 skynet.pack 。
关键代码
function skynet.ret(msg, sz) msg = msg or "" return coroutine_yield("RETURN", msg, sz) end
-- 上面yield "RETURN" 后,会走到这里来
function suspend(co, result, command, param, size)
...
if command == "RETURN" then
local co_session = session_coroutine_id[co]
local co_address = session_coroutine_address[co]
if param == nil or session_response[co] then
error(debug.traceback(co))
end
session_response[co] = true
local ret
if not dead_service[co_address] then
ret = c.send(co_address, skynet.PTYPE_RESPONSE, co_session, param, size) ~= nil
if not ret then
-- If the package is too large, returns nil. so we should report error back
c.send(co_address, skynet.PTYPE_ERROR, co_session, "")
end
elseif size ~= nil then
c.trash(param, size)
ret = false
end
return suspend(co, coroutine.resume(co, ret))
end
...
end
可以看出,这个函数在调用yield后,suspend函数会马上调用resume唤醒它,所以此函数是非阻塞 API 。
使用心得:
一般的使用习惯是把skynet.ret作为回调函数的最后一句,在这句执行完后,整个回调函数就结束了,其协程也将被回收。
类似这样:
skynet.start(function() skynet.dispatch("lua", function(session, address, cmd, ...) local f = command[string.upper(cmd)] if f then skynet.ret(skynet.pack(f(...))) else error(string.format("Unknown command %s", tostring(cmd))) end end) skynet.register "SIMPLEDB" end)
skynet.response
function skynet.response(pack) pack = pack or skynet.pack return coroutine_yield("RESPONSE", pack) end
这个函数的唤醒方式同ret一样,也是在suspend函数内重新唤醒。
同样也是非阻塞API。
关于此函数更详细的分析,请参考这篇文章:skynet.response分析
skynet.sleep
参考。。skynet.wait
参考。。skynet.exit
todofunction skynet.exit() fork_queue = {} -- no fork coroutine can be execute after skynet.exit skynet.send(".launcher","lua","REMOVE",skynet.self(), false) -- report the sources that call me for co, session in pairs(session_coroutine_id) do local address = session_coroutine_address[co] if session~=0 and address then c.redirect(address, 0, skynet.PTYPE_ERROR, session, "") end end for resp in pairs(unresponse) do resp(false) end -- report the sources I call but haven't return local tmp = {} for session, address in pairs(watching_session) do tmp[address] = true end for address in pairs(tmp) do c.redirect(address, 0, skynet.PTYPE_ERROR, 0, "") end c.command("EXIT") -- quit service coroutine_yield "QUIT" end
相关文章推荐
- skynet lua服务callback执行过程
- freeswitch lua/luarun的执行过程
- WINCE的挂起和唤醒过程
- 基于lua协程的AI服务实现
- java线程 - 线程唤醒后并被执行时,是在上次阻塞的代码行重新往下执行,而不是从头开始执行
- 转载:PostgreSQL服务过程中的那些事二:Pg服务进程处理简单查询六:执行器执行
- Bigbluebutton服务执行过程及相关配置文件
- java 线程wait()与notify()的用法(被唤醒后的线程到底重新执行同步代码块还是从那是等待的那里继续执行)
- java 线程wait()与notify()的用法(被唤醒后的线程到底重新执行同步代码块还是从那是等待的那里继续执行)
- Java 多线程使用:线程的挂起与重新唤醒
- 网狐Lua客户端执行过程
- 被唤醒后的线程到底重新执行同步代码块还是从那是等待的那里继续执行
- 一次请求中,经过 nginx+uWSGI+flask应用程序搭建服务的执行过程
- Weblogic挂起、宕机问题分析及优化【转】及执行队列的详细过程
- Java 多线程使用:线程的挂起与重新唤醒
- lua执行字节码的过程介绍
- 网狐Lua客户端执行过程
- c#写服务、exe 可执行程序、windows锁屏的过程中遇到的问题
- lua执行字节码的过程介绍
- docker swarm:执行 service update 过程中服务短暂不能访问的问题