From f0a6754cd15912de529b9662e3e08bfaadd6bf1e Mon Sep 17 00:00:00 2001 From: Alexey Melnichuk Date: Mon, 23 Jun 2014 09:40:01 +0500 Subject: Add. `llthreads2.ex` module. --- .travis.yml | 5 +- lakefile | 6 +- rockspecs/lua-llthreads2-compat-scm-0.rockspec | 3 +- rockspecs/lua-llthreads2-scm-0.rockspec | 3 +- src/lua/llthreads2/ex.lua | 152 +++++++++++++++++++++++++ test/test_threads_ex.lua | 64 +++++++++++ test/test_threads_ex_arg.lua | 26 +++++ test/test_threads_ex_opt.lua | 31 +++++ 8 files changed, 286 insertions(+), 4 deletions(-) create mode 100644 src/lua/llthreads2/ex.lua create mode 100644 test/test_threads_ex.lua create mode 100644 test/test_threads_ex_arg.lua create mode 100644 test/test_threads_ex_opt.lua diff --git a/.travis.yml b/.travis.yml index 5b72267..2b95fdd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ script: - cd test - lua$LUA_SFX test_table_copy.lua - lua$LUA_SFX test_threads.lua - - lua$LUA_SFX test_llthreads.lua + - lua$LUA_SFX test_llthreads.lua 10 # - lua$LUA_SFX test_register_llthreads.lua - lua$LUA_SFX test_join_timeout.lua - lua$LUA_SFX test_join_detach.lua @@ -35,6 +35,9 @@ script: - lua$LUA_SFX test_pass_cfunction.lua - lua$LUA_SFX test_load_llthreads2.lua - lua$LUA_SFX test_alive.lua + - lua$LUA_SFX test_threads_ex.lua 10 + - lua$LUA_SFX test_threads_ex_arg.lua + - lua$LUA_SFX test_threads_ex_opt.lua notifications: email: diff --git a/lakefile b/lakefile index 64bd3fa..1f54996 100644 --- a/lakefile +++ b/lakefile @@ -29,7 +29,7 @@ target('test', install, function() run_test('test_join_timeout.lua') run_test('test_llthreads.lua') run_test('test_table_copy.lua') - run_test('test_threads.lua') + run_test('test_threads.lua 10') run_test('test_join_timeout.lua') run_test('test_join_detach.lua') run_test('test_join_error.lua') @@ -37,6 +37,10 @@ target('test', install, function() run_test('test_logger.lua') run_test('test_pass_cfunction.lua') run_test('test_alive.lua') + run_test('test_threads_ex.lua 10') + run_test('test_threads_ex_arg.lua') + run_test('test_threads_ex_opt.lua') + if not test_summary() then quit("test fail") diff --git a/rockspecs/lua-llthreads2-compat-scm-0.rockspec b/rockspecs/lua-llthreads2-compat-scm-0.rockspec index ac81a15..67a062d 100644 --- a/rockspecs/lua-llthreads2-compat-scm-0.rockspec +++ b/rockspecs/lua-llthreads2-compat-scm-0.rockspec @@ -39,6 +39,7 @@ build = { llthreads = { sources = { "src/l52util.c", "src/llthread.c" }, defines = { "LLTHREAD_MODULE_NAME=llthreads" }, - } + }, + ["llthreads2.ex"] = "src/lua/llthreads2/ex.lua", } } \ No newline at end of file diff --git a/rockspecs/lua-llthreads2-scm-0.rockspec b/rockspecs/lua-llthreads2-scm-0.rockspec index 87f9b15..0795eca 100644 --- a/rockspecs/lua-llthreads2-scm-0.rockspec +++ b/rockspecs/lua-llthreads2-scm-0.rockspec @@ -39,6 +39,7 @@ build = { llthreads2 = { sources = { "src/l52util.c", "src/llthread.c" }, defines = { "LLTHREAD_MODULE_NAME=llthreads2" }, - } + }, + ["llthreads2.ex"] = "src/lua/llthreads2/ex.lua", } } \ No newline at end of file diff --git a/src/lua/llthreads2/ex.lua b/src/lua/llthreads2/ex.lua new file mode 100644 index 0000000..26e1a46 --- /dev/null +++ b/src/lua/llthreads2/ex.lua @@ -0,0 +1,152 @@ +-- +-- wraps the low-level threads object. +-- + +-- +-- Note! Define this function prior all `local` definitions +-- to prevent use upvalue by accident +-- +local bootstrap_code = require"string".dump(function(lua_init, prelude, code, ...) + local loadstring = loadstring or load + local unpack = table.unpack or unpack + + local function load_src(str) + local f, n + if str:sub(1,1) == '@' then + n = str:sub(2) + f = assert(loadfile(n)) + else + n = '=(loadstring)' + f = assert(loadstring(str)) + end + return f, n + end + + local function pack_n(...) + return { n = select("#", ...), ... } + end + + local function unpack_n(t) + return unpack(t, 1, t.n) + end + + if lua_init and #lua_init > 0 then + local init = load_src(lua_init) + init() + end + + local args + + if prelude and #prelude > 0 then + prelude = load_src(prelude) + args = pack_n(prelude(...)) + else + args = pack_n(...) + end + + local func + func, args[0] = load_src(code) + + _G.arg = args + arg = args + + return func(unpack_n(args)) +end) + +local ok, llthreads = pcall(require, "llthreads2") +if not ok then llthreads = require"llthreads" end + +local os = require"os" +local string = require"string" +local table = require"table" + +local setmetatable, tonumber, assert = setmetatable, tonumber, assert + +------------------------------------------------------------------------------- +local LUA_INIT = "LUA_INIT" do + +local lua_version_t +local function lua_version() + if not lua_version_t then + local version = assert(_G._VERSION) + local maj,min = version:match("^Lua (%d+)%.(%d+)$") + if maj then lua_version_t = {tonumber(maj),tonumber(min)} + elseif not math.mod then lua_version_t = {5,2} + elseif table.pack and not pack then lua_version_t = {5,2} + else lua_version_t = {5,2} end + end + return lua_version_t[1], lua_version_t[2] +end + +local LUA_MAJOR, LUA_MINOR = lua_version() +local IS_LUA_51 = (LUA_MAJOR == 5) and (LUA_MINOR == 1) + +local LUA_INIT_VER +if not IS_LUA_51 then + LUA_INIT_VER = LUA_INIT .. "_" .. LUA_MAJOR .. "_" .. LUA_MINOR +end + +LUA_INIT = LUA_INIT_VER and os.getenv( LUA_INIT_VER ) or os.getenv( LUA_INIT ) or "" + +end +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +local thread_mt = {} do +thread_mt.__index = thread_mt + +function thread_mt:start(...) + local ok, err = self.thread:start(...) + if not ok then return nil, err end + return self +end + +function thread_mt:join(...) + return self.thread:join(...) +end + +function thread_mt:alive() + return self.thread:alive() +end + +end +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +local threads = {} do + +local function new_thread(prelude, lua_init, code, ...) + if type(lua_init) == "function" then + lua_init = string.dump(lua_init) + end + + if type(prelude) == "function" then + prelude = string.dump(prelude) + end + + if type(code) == "function" then + code = string.dump(code) + end + + local thread = llthreads.new(bootstrap_code, lua_init, prelude, code, ...) + return setmetatable({ + thread = thread, + }, thread_mt) +end + +threads.new = function (code, ...) + assert(code) + + if type(code) == "table" then + local source = assert(code.source or code[1]) + local init = (code.lua_init == nil) and LUA_INIT or code.lua_init + return new_thread(code.prelude, init, source, ...) + end + + return new_thread(nil, LUA_INIT, code, ...) +end + +end +------------------------------------------------------------------------------- + +return threads \ No newline at end of file diff --git a/test/test_threads_ex.lua b/test/test_threads_ex.lua new file mode 100644 index 0000000..3a6cc41 --- /dev/null +++ b/test/test_threads_ex.lua @@ -0,0 +1,64 @@ +-- Copyright (c) 2011 by Ross Anderson +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +-- THE SOFTWARE. + +-- Sub-thread processing example in Lua using llthreads - 1,000 quick sub-thread execution + +-- luajit sub_threads.lua + +-- level 0 string literal enclosure [[ ]] of child execution code +local thread_code = function(num_threads, ...) + print("CHILD: received from ROOT params:", num_threads, ...) + local llthreads = require"llthreads.ex" -- need to re-declare this under this scope + local t = {} -- thread storage table + + -- create a new child sub-thread execution code - it requires level 1 literal string [=[ ]=] enclosures, level 2 would be [==[ ]==] + local executed_child_code = function(...) + return "Hello from child sub-thread, new input params:", ... + end + + -- create 1000 sub-threads - which creates an incremental 30% / 20% utilization spike on the two AMD cpu cores + print("CHILD: Create sub threads:", num_threads) + for i=1,num_threads do + -- create child sub-thread with code to execute and the input parmeters + local thread = llthreads.new(executed_child_code , "number:", 1000 + i, "nil:", nil, "bool:", true) + assert(thread:start()) -- start new child sub-thread + table.insert(t, thread) -- append the thread at the end of the thread table + end + + -- wait (block) for all child sub-threads to complete before returning to ROOT + while true do + -- always wait on the first element, since order is not important + print("CHILD: sub-thread returned: ", t[1]:join()) + table.remove(t,1) -- always remove the first element + if (#t == 0) then break end + end + return ... -- return the parents' input params back to the root +end + +local llthreads = require"llthreads.ex" + +local num_threads = tonumber(arg[1] or 1000) + +-- create child thread. +local thread = llthreads.new(thread_code, num_threads, "number:", 1000, "nil:", nil, "bool:", true) +-- start joinable child thread. +assert(thread:start()) +-- wait for all child and child sub-threads to finish +print("ROOT: child returned: ", thread:join()) diff --git a/test/test_threads_ex_arg.lua b/test/test_threads_ex_arg.lua new file mode 100644 index 0000000..44d3dd8 --- /dev/null +++ b/test/test_threads_ex_arg.lua @@ -0,0 +1,26 @@ +local thread_code = function(...) + local function assert_equal(name, a, b, ...) + if a == b then return b, ... end + print(name .. " Fail! Expected `" .. tostring(a) .. "` got `" .. tostring(b) .. "`") + os.exit(1) + end + + local a,b,c,d,e,f = ... + assert_equal("1:", 1 , a ) + assert_equal("2:", nil , b ) + assert_equal("3:", 'hello' , c ) + assert_equal("4:", nil , d ) + assert_equal("5:", 2 , e ) + assert_equal("6:", nil , f ) + assert_equal("#:", 6 , select("#", ...)) +end + +local llthreads = require"llthreads.ex" + +local thread = llthreads.new(thread_code, 1, nil, 'hello', nil, 2, nil) + +assert(thread:start()) + +assert(thread:join()) + +print("done!") \ No newline at end of file diff --git a/test/test_threads_ex_opt.lua b/test/test_threads_ex_opt.lua new file mode 100644 index 0000000..2e1d715 --- /dev/null +++ b/test/test_threads_ex_opt.lua @@ -0,0 +1,31 @@ +local thread_code = function(...) + local function assert_equal(name, a, b, ...) + if a == b then return b, ... end + print(name .. " Fail! Expected `" .. tostring(a) .. "` got `" .. tostring(b) .. "`") + os.exit(1) + end + + local a,b,c,d,e,f = ... + assert_equal("1:", 1 , a ) + assert_equal("2:", nil , b ) + assert_equal("3:", 'hello' , c ) + assert_equal("4:", nil , d ) + assert_equal("5:", 2 , e ) + assert_equal("6:", nil , f ) + assert_equal("#:", 6 , select("#", ...)) +end + +local llthreads = require"llthreads.ex" + +-- pass `prelude` function that change thread arguments +local thread = llthreads.new({thread_code, prelude = function(...) + return 1, nil, 'hello', ... +end}, nil, 2, nil) + +local a = assert(thread:start()) + +assert(thread:join()) + +assert(a == thread) + +print("done!") \ No newline at end of file -- cgit v1.2.3-55-g6feb