Effil
Multithreading support for Lua
Install / Use
/learn @effil/EffilREADME
Effil
| Linux + MacOS | Windows |
| ------------- | ------- |
| |
|
Effil is a multithreading library for Lua. It allows to spawn native threads and safe data exchange. Effil has been designed to provide clear and simple API for lua developers.
Effil supports lua 5.1, 5.2, 5.3 and LuaJIT. Requires C++14 compiler compliance. Tested with GCC 4.9+, clang 3.8 and Visual Studio 2015.
Table Of Contents
- How to install
- Quick guide
- Important notes
- Blocking and nonblocking operations
- Function's upvalues
- Thread cancellation and pausing
- API Reference
How to install
Build from src on Linux and Mac
git clone --recursive https://github.com/effil/effil effilcd effil && mkdir build && cd buildcmake .. && make install- Copy effil.so to your project.
From lua rocks
luarocks install effil
Quick guide
As you may know there are not much script languages with real multithreading support (Lua/Python/Ruby and etc has global interpreter lock aka GIL). Effil solves this problem by running independent Lua VM instances in separate native threads and provides robust communicating primitives for creating threads and data sharing.
Effil library provides three major abstractions:
effil.thread- provides API for threads management.effil.table- provides API for tables management. Tables can be shared between threads.effil.channel- provides First-In-First-Out container for sequential data exchange.
And bunch of utilities to handle threads and tables as well.
Examples
<details> <summary><b>Spawn the thread</b></summary> <p>local effil = require("effil")
function bark(name)
print(name .. " barks from another thread!")
end
-- run funtion bark in separate thread with name "Spaky"
local thr = effil.thread(bark)("Sparky")
-- wait for completion
thr:wait()
Output:
Sparky barks from another thread!
local effil = require("effil")
-- channel allow to push data in one thread and pop in other
local channel = effil.channel()
-- writes some numbers to channel
local function producer(channel)
for i = 1, 5 do
print("push " .. i)
channel:push(i)
end
channel:push(nil)
end
-- read numbers from channels
local function consumer(channel)
local i = channel:pop()
while i do
print("pop " .. i)
i = channel:pop()
end
end
-- run producer
local thr = effil.thread(producer)(channel)
-- run consumer
consumer(channel)
thr:wait()
Output:
push 1
push 2
pop 1
pop 2
push 3
push 4
push 5
pop 3
pop 4
pop 5
</p>
</details>
<details>
<summary><b>Sharing data with effil.table</b></summary>
<p>
effil = require("effil")
-- effil.table transfers data between threads
-- and behaves like regualr lua table
local storage = effil.table { string_field = "first value" }
storage.numeric_field = 100500
storage.function_field = function(a, b) return a + b end
storage.table_field = { fist = 1, second = 2 }
function check_shared_table(storage)
print(storage.string_field)
print(storage.numeric_field)
print(storage.table_field.first)
print(storage.table_field.second)
return storage.function_field(1, 2)
end
local thr = effil.thread(check_shared_table)(storage)
local ret = thr:get()
print("Thread result: " .. ret)
Output:
first value
100500
1
2
Thread result: 3
</p>
</details>
Important notes
Effil allows to transmit data between threads (Lua interpreter states) using effil.channel, effil.table or directly as parameters of effil.thread.
- Primitive types are transmitted 'as is' by copy:
nil,boolean,number,string - Functions are dumped using
lua_dump. Upvalues are captured according to the rules.- C functions (for which
lua_iscfunctionreturns true) are transmitted just by a pointer usinglua_tocfunction(in original lua_State) and lua_pushcfunction (in new lua_State).
caution: in LuaJIT standard functions like tonumber are not real C function, solua_iscfunctionreturns true butlua_tocfunctionreturns nullptr. Due to that we don't find way to transmit it between lua_States.
- C functions (for which
- Userdata and Lua threads (coroutines) are not supported.
- Tables are serialized to
effil.tablerecursively. So, any Lua table becomeseffil.table. Table serialization may take a lot of time for big table. Thus, it's better to put data directly toeffil.tableavoiding a table serialization. Let's consider 2 examples:
-- Example #1
t = {}
for i = 1, 100 do
t[i] = i
end
shared_table = effil.table(t)
-- Example #2
t = effil.table()
for i = 1, 100 do
t[i] = i
end
In the example #1 we create regular table, fill it and convert it to effil.table. In this case Effil needs to go through all table fields one more time. Another way is example #2 where we firstly created effil.table and after that we put data directly to effil.table. The 2nd way pretty much faster try to follow this principle.
Blocking and nonblocking operations:
All operations which use time metrics can be blocking or non blocking and use following API:
(time, metric) where metric is time interval like 's' (seconds) and time is a number of intervals.
Example:
thread:get()- infinitely wait for thread completion.thread:get(0)- non blocking get, just check is thread finished and returnthread:get(50, "ms")- blocking wait for 50 milliseconds.
List of available time intervals:
ms- milliseconds;s- seconds (default);m- minutes;h- hours.
All blocking operations (even in non blocking mode) are interruption points. Thread hanged in such operation can be interrupted by invoking thread:cancel() method.
<details> <summary><b>Example</b></summary> <p>local effil = require "effil"
local worker = effil.thread(function()
effil.sleep(999) -- worker will hang for 999 seconds
end)()
worker:cancel(1) -- returns true, cause blocking operation was interrupted and thread was cancelled
</p>
</details>
Function's upvalues
Working with functions Effil serializes and deserializes them using lua_dump and lua_load methods. All function's upvalues are stored following the same rules as usual. If function has upvalue of unsupported type this function cannot be transmitted to Effil. You will get error in that
