You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

578 lines
26 KiB

--===========================================================================--
-- --
-- System.Threading --
-- --
--===========================================================================--
--===========================================================================--
-- Author : kurapica125@outlook.com --
-- URL : http://github.com/kurapica/PLoop --
-- Create Date : 2013/08/13 --
-- Update Date : 2020/06/25 --
-- Version : 1.1.2 --
--===========================================================================--
PLoop(function(_ENV)
__Final__() __Sealed__() __Abstract__()
class "System.Threading" (function(_ENV)
Environment.RegisterGlobalNamespace("System.Threading")
--- the thread pool used to generate and recycle coroutines
__Sealed__() __Final__() __NoRawSet__(false) __NoNilValue__(false)
class "ThreadPool" (function(_ENV)
if Platform.ENABLE_CONTEXT_FEATURES then
extend "System.IContext"
end
export {
create = coroutine.create,
resume = coroutine.resume,
running = coroutine.running,
status = coroutine.status,
wrap = coroutine.wrap,
yield = coroutine.yield,
tinsert = table.insert,
tremove = table.remove,
getmetatable = getmetatable,
getlocal = _G.debug and debug.getlocal or false,
pcall = pcall,
loadsnippet = Toolset.loadsnippet,
safeset = Toolset.safeset,
select = select,
tblconcat = table.concat,
strformat = string.format,
GetContext = Context.GetCurrentContext,
PREPARE_CONFIRM = Toolset.fakefunc,
ThreadPool
}
-----------------------------------------------------------
-- helpers --
-----------------------------------------------------------
local _PassArgs = { [0] = function(thread, func) return func(yield(thread)) end }
local _PassGenCode = [[return function (thread, func, %s) return func(%s, yield(thread)) end]]
local function newPass(count)
local args = {}
for i = 1, count do args[i] = "arg" .. i end
args = tblconcat(args, ", ")
local pass = loadsnippet(strformat(_PassGenCode, args, args), "Thread_Pass_" .. count, _ENV)()
_PassArgs = safeset(_PassArgs, count, pass)
return pass
end
local function returnwithrecycle(pool, thread, asiter, ...)
if #pool < pool.PoolSize then tinsert(pool, thread) end -- recyle the thread
if asiter then
if select("#", ...) > 0 then yield(...) end -- return the value
yield() -- make sure the iterator don't resume again
yield()
else
yield(...)
end
end
local function preparefunc(pool, thread, func, asiter, ...)
if not func then return end
local cnt = select("#", ...)
returnwithrecycle(pool, thread, asiter, (_PassArgs[cnt] or newPass(cnt))(thread, func, ...))
end
local function recyclethread(pool, thread)
while true do preparefunc(pool, thread, yield(PREPARE_CONFIRM)) end
end
local function newrecyclethread(self)
local thread = tremove(self)
-- Check Re-usable
if thread then for i= 1, 3 do if thread() == PREPARE_CONFIRM then return thread end end end
-- Create the new thread
thread = wrap(recyclethread)
thread(self, thread) -- pass the pool and thread to be recycled
return thread
end
-- make sure we can get the thread pool in the func
local function retresult(...) return ... end
local function poolthread(pool, func) return retresult(func(yield())) end
local function newpoolthread(self, func, aswrap)
if aswrap then
local thread = wrap(poolthread)
thread(self, func)
return thread
else
local thread = create(poolthread)
resume(thread, self, func)
return thread
end
end
local getCurrentPool = getlocal and function()
local curr, ismain = running()
if not curr or ismain then return end
local stack = 5
local n, v = getlocal(stack, 1)
while true do
if n == "pool" and getmetatable(v) == ThreadPool then
return v
end
stack = stack + 1
n, v = getlocal(stack, 1)
end
end or Toolset.fakefunc
-----------------------------------------------------------
-- method --
-----------------------------------------------------------
--- Get the thread or wrap function for the function, this
-- thread should be processed under the thread pool
-- @param func the target function
-- @return wrap the wrap function or thread
__Arguments__{ Function, Boolean/nil }
GetThread = newpoolthread
--- safe call the function within a coroutine, if the caller is already
-- in a coroutine, no new coroutine will be used
__Arguments__{ Function, Any * 0 }
function SafeThreadCall(self, func, ...)
local curr, ismain = running()
if curr and not ismain then return func(...) end
local thread = newrecyclethread(self)
return thread(func)(...)
end
--- call the function with arguments in a recyclable coroutine
-- @param func the target function
-- @param ... the arguments
-- @usage function a(...)
-- return coroutine.running(), ...
-- end
--
-- print(pool:ThreadCall(a, 1, 2, 3))
--
-- -- Oupput
-- -- thread: 00F95100 1 2 3
__Arguments__{ Function, Any * 0 }
function ThreadCall(self, func, ...)
local thread = newrecyclethread(self)
return thread(func)(...)
end
--- Run the function as an iterator in a recyclable coroutine
-- @param func the function contains yield instructions
-- @param ... The arguments
-- @usage function a(start, endp)
-- for i = start, endp do
-- coroutine.yield(i, "i_"..i)
-- end
-- end
--
-- for k, v in pool:GetIterator(a), 1, 3 do print(k, v) end
--
-- -- Oupput
-- -- 1 i_1
-- -- 2 i_2
-- -- 3 i_3
--
-- -- Also can be used as
-- for k, v in Threading.Iterator(a, 1, 3) do print(k, v) end
if Platform.THREAD_SAFE_ITERATOR then
local function safeIteratorCall(msg, ...)
if msg == "cannot resume dead coroutine" then return end
return msg, ...
end
__Arguments__{ Function, Any * 0 }
function GetIterator(self, func, ...)
local thread = newrecyclethread(self)
local wrap = thread(func, true, ...)
return function(...)
return safeIteratorCall(wrap(...))
end
end
else
__Arguments__{ Function, Any * 0 }
function GetIterator(self, func, ...)
local thread = newrecyclethread(self)
return thread(func, true, ...)
end
end
-----------------------------------------------------------
-- static property --
-----------------------------------------------------------
local DEFAULT_POOL_SIZE = (Platform.MULTI_OS_THREAD or Platform.ENABLE_CONTEXT_FEATURES) and 0 or nil
--- the default thread pool, can't be used in multi os thread mode
__Static__() property "Default" { set = false, default = function() return ThreadPool{ PoolSize = DEFAULT_POOL_SIZE } end }
--- the current thread pool if not use default
__Static__() property "Current" {
get = function()
local ok, ret = pcall(getCurrentPool)
if ok and ret then return ret end
local context = GetContext()
if context then
local cpool = context[ThreadPool]
if not cpool then
cpool = ThreadPool()
context[ThreadPool] = cpool
end
return cpool
end
return ThreadPool.Default
end
}
-----------------------------------------------------------
-- property --
-----------------------------------------------------------
--- the max pool size for idle coroutines
property "PoolSize" { type = NaturalNumber, default = Platform.THREAD_POOL_MAX_SIZE }
end)
--- represent an interface for lock manager
__Sealed__() __AnonymousClass__()
interface "ILockManager" (function(_ENV)
export {
GetContext = Context.GetCurrentContext,
fakeobj = {},
error = error,
tostring = tostring,
pcall = pcall,
ILockManager,
}
local function releaseLock(manager, context, key, obj, result, ...)
if context and context[ILockManager] then
context[ILockManager][key] = nil
end
local ok, err = manager:Release(obj, key)
if not result then error(..., 0) end
if not ok then
return error("Usage: ILockManager:Release(lockobj, key) - Release key failed:" .. tostring(err))
end
return ...
end
-----------------------------------------------------------------------
-- static property --
-----------------------------------------------------------------------
--- the unique lock manager
__Static__() property "Manager" { type = ILockManager, handler = function(self, new, old) if old then old:Dispose() end end, default = function() return ILockManager() end }
-----------------------------------------------------------------------
-- static method --
-----------------------------------------------------------------------
--- Lock with a key and process the target function
-- @param key the lock key
-- @param func the function
-- @param ... the function arguments
__Static__() function RunWithLock(key, func, ...)
local context = GetContext()
if context and context[ILockManager] and context[ILockManager][key] then
-- Already locked, continue job
return func(...)
end
local manager = ILockManager.Manager
-- lock the key
local lockObj, err = manager:Lock(key)
if not lockObj then
return error("Usage: ILockManager:Lock(key) - Lock key failed:" .. tostring(err))
end
if context then
context[ILockManager] = context[ILockManager] or {}
context[ILockManager][key] = true
end
return releaseLock(manager, context, key, lockObj, pcall(func, ...))
end
--- Try lock with a key and process the target function
-- @param key the lock key
-- @param func the function
-- @param ... the function arguments
__Static__() function TryRunWithLock(key, func, ...)
local context = GetContext()
if context and context[ILockManager] and context[ILockManager][key] then
-- Already locked, continue job
return func(...)
end
local manager = ILockManager.Manager
-- lock the key
local lockObj, err = manager:TryLock(key)
if not lockObj then return end
if context then
context[ILockManager] = context[ILockManager] or {}
context[ILockManager][key] = true
end
return releaseLock(manager, context, key, lockObj, pcall(func, ...))
end
-----------------------------------------------------------------------
-- method --
-----------------------------------------------------------------------
--- Lock with a key and return a lock object to release
-- @param key the lock key
-- @return object the lock object
-- @return error the error message if failed
__Abstract__() function Lock(self, key)
return fakeobj
end
--- Try lock with a key and return a lock object to release
-- @param key the lock key
-- @return object the lock object
-- @return message the error message if failed
__Abstract__() function TryLock(self, key)
return fakeobj
end
--- Release the lock object
-- @param object the lock object
-- @param key the lock key
-- @return bool true if released
-- @return message the error message if failed
__Abstract__() function Release(self, obj, key)
return true
end
-----------------------------------------------------------------------
-- initializer --
-----------------------------------------------------------------------
function __init(self)
ILockManager.Manager = self
end
end)
--- specify a method or function as asynchronous
__Sealed__() __Final__()
class "__Async__" (function(_ENV)
extend "IInitAttribute"
export { ThreadPool }
local wraptarget = Platform.ENABLE_CONTEXT_FEATURES and function(target)
return function(...) return ThreadPool.Current:ThreadCall(target, ...) end
end or function(target)
return function(...) return ThreadPool.Default:ThreadCall(target, ...) end
end
local safewrap = Platform.ENABLE_CONTEXT_FEATURES and function(target)
return function(...) return ThreadPool.Current:SafeThreadCall(target, ...) end
end or function(target)
return function(...) return ThreadPool.Default:SafeThreadCall(target, ...) end
end
-----------------------------------------------------------
-- method --
-----------------------------------------------------------
--- modify the target's definition
-- @param target the target
-- @param targettype the target type
-- @param definition the target's definition
-- @param owner the target's owner
-- @param name the target's name in the owner
-- @param stack the stack level
-- @return definition the new definition
function InitDefinition(self, target, targettype, definition, owner, name, stack)
if self.SafeMode then
return safewrap(definition)
else
return wraptarget(definition)
end
end
-----------------------------------------------------------
-- property --
-----------------------------------------------------------
--- the attribute target
property "AttributeTarget" { type = AttributeTargets, default = AttributeTargets.Method + AttributeTargets.Function }
--- the attribute's priority
property "Priority" { type = AttributePriority, default = AttributePriority.Lower }
--- Whether use safe mode
property "SafeMode" { type = Boolean }
-----------------------------------------------------------
-- constructor --
-----------------------------------------------------------
function __ctor(self, safe) self.SafeMode = safe and true or false end
end)
--- specify a method or function as iterator who use yield to generate values
__Sealed__() __Final__()
class "__Iterator__" (function(_ENV)
extend "IInitAttribute"
export { ThreadPool }
local wraptarget = Platform.ENABLE_CONTEXT_FEATURES and function(target)
return function(...) return ThreadPool.Current:GetIterator(target, ...) end
end or function(target)
return function(...) return ThreadPool.Default:GetIterator(target, ...) end
end
-----------------------------------------------------------
-- method --
-----------------------------------------------------------
--- modify the target's definition
-- @param target the target
-- @param targettype the target type
-- @param definition the target's definition
-- @param owner the target's owner
-- @param name the target's name in the owner
-- @param stack the stack level
-- @return definition the new definition
function InitDefinition(self, target, targettype, definition, owner, name, stack)
return wraptarget(definition)
end
-----------------------------------------------------------
-- property --
-----------------------------------------------------------
--- the attribute target
property "AttributeTarget" { type = AttributeTargets, default = AttributeTargets.Method + AttributeTargets.Function }
--- the attribute's priority
property "Priority" { type = AttributePriority, default = AttributePriority.Lower }
end)
--- specify a method or function to run with a lock key
__Sealed__() __Final__()
class "__Lock__" (function(_ENV)
extend "IInitAttribute"
export { RunWithLock = ILockManager.RunWithLock }
local wraptarget = Platform.ENABLE_THREAD_LOCK and function(target, key)
return function(...) return RunWithLock(key, target, ...) end
end or Toolset.fakefunc
-----------------------------------------------------------
-- method --
-----------------------------------------------------------
--- modify the target's definition
-- @param target the target
-- @param targettype the target type
-- @param definition the target's definition
-- @param owner the target's owner
-- @param name the target's name in the owner
-- @param stack the stack level
-- @return definition the new definition
function InitDefinition(self, target, targettype, definition, owner, name, stack)
return wraptarget(definition, self[1])
end
-----------------------------------------------------------
-- property --
-----------------------------------------------------------
--- the attribute target
property "AttributeTarget" { type = AttributeTargets, default = AttributeTargets.Method + AttributeTargets.Function }
--- the attribute's priority
property "Priority" { type = AttributePriority, default = AttributePriority.Lower }
-----------------------------------------------------------
-- constructor --
-----------------------------------------------------------
__Arguments__{ Any }
function __new(_, key)
return { key }
end
end)
--- specify a method or function to run with a lock key
__Sealed__() __Final__()
class "__TryLock__" (function(_ENV)
extend "IInitAttribute"
export { TryRunWithLock = ILockManager.TryRunWithLock }
local wraptarget = Platform.ENABLE_THREAD_LOCK and function(target, key)
return function(...) return TryRunWithLock(key, target, ...) end
end or Toolset.fakefunc
-----------------------------------------------------------
-- method --
-----------------------------------------------------------
--- modify the target's definition
-- @param target the target
-- @param targettype the target type
-- @param definition the target's definition
-- @param owner the target's owner
-- @param name the target's name in the owner
-- @param stack the stack level
-- @return definition the new definition
function InitDefinition(self, target, targettype, definition, owner, name, stack)
return wraptarget(definition, self[1])
end
-----------------------------------------------------------
-- property --
-----------------------------------------------------------
--- the attribute target
property "AttributeTarget" { type = AttributeTargets, default = AttributeTargets.Method + AttributeTargets.Function }
--- the attribute's priority
property "Priority" { type = AttributePriority, default = AttributePriority.Lower }
-----------------------------------------------------------
-- constructor --
-----------------------------------------------------------
__Arguments__{ Any }
function __new(_, key)
return { key }
end
end)
-----------------------------------------------------------------------
-- static method --
-----------------------------------------------------------------------
-- Run a function with arguments async
__Static__() __Async__() __Arguments__{ Function, Any * 0 }
function RunAsync(func, ...)
return func(...)
end
-- Run a function with arguments as iterator
__Static__() __Iterator__() __Arguments__{ Function, Any * 0 }
function RunIterator(func, ...)
return func(...)
end
end)
end)