리눅스 + 맥OS | 윈도우 |
---|---|
Effil은 Lua용 멀티스레딩 라이브러리입니다. 네이티브 스레드를 생성하고 안전한 데이터 교환을 허용합니다. Effil은 Lua 개발자에게 명확하고 간단한 API를 제공하도록 설계되었습니다.
Effil은 lua 5.1, 5.2, 5.3 및 LuaJIT를 지원합니다. C++14 컴파일러 준수가 필요합니다. GCC 4.9+, clang 3.8 및 Visual Studio 2015에서 테스트되었습니다.
git clone --recursive https://github.com/effil/effil effil
cd effil && mkdir build && cd build
cmake .. && make install
luarocks install effil
아시다시피 실제 멀티스레딩을 지원하는 스크립트 언어는 많지 않습니다(Lua/Python/Ruby 등에는 GIL이라고도 불리는 전역 인터프리터 잠금 기능이 있습니다). Effil은 별도의 기본 스레드에서 독립적인 Lua VM 인스턴스를 실행하여 이 문제를 해결하고 스레드 생성 및 데이터 공유를 위한 강력한 통신 기본 요소를 제공합니다.
Effil 라이브러리는 세 가지 주요 추상화를 제공합니다.
effil.thread
- 스레드 관리를 위한 API를 제공합니다.effil.table
- 테이블 관리를 위한 API를 제공합니다. 스레드 간에 테이블을 공유할 수 있습니다.effil.channel
- 순차 데이터 교환을 위한 First-In-First-Out 컨테이너를 제공합니다.그리고 스레드와 테이블을 처리하는 유틸리티도 많이 있습니다.
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 ()
출력: 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 ()
산출:
push 1
push 2
pop 1
pop 2
push 3
push 4
push 5
pop 3
pop 4
pop 5
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 )
산출:
first value
100500
1
2
Thread result: 3
Effil을 사용하면 effil.channel
, effil.table
사용하거나 effil.thread
의 매개변수로 직접 스레드(Lua 인터프리터 상태) 간에 데이터를 전송할 수 있습니다.
nil
, boolean
, number
, string
lua_dump
사용하여 덤프됩니다. Upvalue는 규칙에 따라 캡처됩니다.lua_iscfunction
true를 반환하는) C 함수는 lua_tocfunction
(원래 lua_State에서)과 lua_pushcfunction(새 lua_State에서)을 사용하는 포인터만으로 전송됩니다.lua_iscfunction
true를 반환하지만 lua_tocfunction
nullptr을 반환합니다. 그 때문에 우리는 lua_States 간에 이를 전송할 방법을 찾지 못했습니다.effil.table
에 재귀적으로 직렬화됩니다. 따라서 모든 Lua 테이블은 effil.table
됩니다. 큰 테이블의 경우 테이블 직렬화에 많은 시간이 걸릴 수 있습니다. 따라서 테이블 직렬화를 피하고 데이터를 effil.table
에 직접 저장하는 것이 좋습니다. 두 가지 예를 고려해 보겠습니다. -- 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
예제 #1에서는 일반 테이블을 만들고 채우고 effil.table
로 변환합니다. 이 경우 Effil은 모든 테이블 필드를 한 번 더 살펴봐야 합니다. 또 다른 방법은 먼저 effil.table
생성한 후 데이터를 effil.table
에 직접 저장하는 예제 #2입니다. 두 번째 방법은 이 원칙을 따르는 것이 훨씬 더 빠릅니다.
시간 측정항목을 사용하는 모든 작업은 차단 또는 비차단이 가능하며 다음 API (time, metric)
를 사용할 수 있습니다. 여기서 metric
은 's'
(초)와 같은 시간 간격이고 time
은 간격 수입니다.
예:
thread:get()
- 스레드 완료를 무한히 기다립니다.thread:get(0)
- get을 차단하지 않고 스레드가 완료되었는지 확인하고 반환합니다.thread:get(50, "ms")
- 차단은 50밀리초 동안 대기합니다.사용 가능한 시간 간격 목록:
ms
- 밀리초;s
- 초(기본값)m
- 분;h
시간.모든 차단 작업(비차단 모드에서도)은 중단 지점입니다. 이러한 작업에서 중단된 스레드는 thread:cancel() 메서드를 호출하여 중단될 수 있습니다.
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
함수 작업 Effil은 lua_dump
및 lua_load
메서드를 사용하여 함수를 직렬화 및 역직렬화합니다. 모든 함수의 upvalue는 평소와 동일한 규칙에 따라 저장됩니다. 함수에 지원되지 않는 유형의 upvalue가 있는 경우 이 함수를 Effil로 전송할 수 없습니다. 이 경우 오류가 발생합니다.
함수 작업 Effil은 함수 환경( _ENV
)도 저장할 수 있습니다. 환경을 일반 테이블로 간주하면 Effil은 다른 테이블과 동일한 방식으로 이를 저장합니다. 그러나 전역 _G
저장하는 것은 의미가 없으므로 몇 가지 구체적인 사항이 있습니다.
_ENV ~= _G
). effil.thread
는 스레드 객체 thread:cancel()
및 thread:pause()
의 해당 메서드를 사용하여 일시 중지하고 취소할 수 있습니다.
중단하려는 스레드는 명시적 실행 지점과 암시적 실행 지점의 두 가지 실행 지점에서 중단될 수 있습니다.
명시적 포인트는 effil.yield()
입니다.
local thread = effil . thread ( function ()
while true do
effil . yield ()
end
-- will never reach this line
end )()
thread : cancel ()
암시적 지점은 LUA_MASKCOUNT와 함께 lua_sethook을 사용하여 설정된 lua 디버그 후크 호출입니다.
암시적 포인트는 선택 사항이며 thread_runner.step > 0인 경우에만 활성화됩니다.
local thread_runner = effil . thread ( function ()
while true do
end
-- will never reach this line
end )
thread_runner . step = 10
thread = thread_runner ()
thread : cancel ()
또한 차단 또는 비차단 대기 작업에서 스레드를 취소할 수 있습니다(일시 중지할 수는 없음).
local channel = effil . channel ()
local thread = effil . thread ( function ()
channel : pop () -- thread hangs waiting infinitely
-- will never reach this line
end )()
thread : cancel ()
취소는 어떻게 이루어지나요?
스레드를 취소하면 중단 지점에 도달하면 "Effil: thread is cancelled"
메시지와 함께 lua error
생성됩니다. 이는 pcall
사용하여 이 오류를 잡을 수 있지만 스레드는 다음 중단 지점에서 새로운 오류를 생성한다는 것을 의미합니다.
자신의 오류를 포착하고 싶지만 취소 오류를 전달하려는 경우 effil.pcall()을 사용할 수 있습니다.
취소된 스레드의 상태는 취소 오류로 완료된 경우에만 cancelled
과 같습니다. 이는 취소 오류를 포착하면 스레드가 completed
상태로 완료되거나 다른 오류가 있을 경우 failed
상태로 완료될 수 있음을 의미합니다.
effil.thread
는 스레드를 생성하는 방법입니다. 스레드를 중지, 일시 중지, 재개 및 취소할 수 있습니다. 스레드를 사용한 모든 작업은 동기식(선택적 시간 초과 포함) 또는 비동기식일 수 있습니다. 각 스레드는 자체 Lua 상태로 실행됩니다.
스레드를 통해 데이터를 전송하려면 effil.table
및 effil.channel
사용하십시오. 여기에서 스레드 사용 예를 참조하세요.
runner = effil.thread(func)
스레드 러너를 생성합니다. Runner는 호출할 때마다 새 스레드를 생성합니다.
입력 : func - Lua 함수
출력 : 러너 - 새 스레드를 구성하고 실행하는 스레드 러너 개체
새 스레드를 구성하고 실행할 수 있습니다.
thread = runner(...)
별도의 스레드에서 지정된 인수를 사용하여 캡처된 함수를 실행하고 스레드 핸들을 반환합니다.
input : 캡처된 함수에 필요한 인수 수입니다.
출력 : 스레드 핸들 개체입니다.
runner.path
새 상태에 대한 Lua package.path
값입니다. 기본값은 상위 상태로부터 package.path
상속합니다.
runner.cpath
새 상태에 대한 Lua package.cpath
값입니다. 기본값은 상위 상태로부터 package.cpath
상속합니다.
runner.step
취소 지점(스레드가 중지되거나 일시 중지될 수 있는 지점) 사이의 lua 명령 수입니다. 기본값은 200입니다. 이 값이 0이면 스레드는 명시적인 취소 지점만 사용합니다.
스레드 핸들은 스레드와의 상호 작용을 위한 API를 제공합니다.
status, err, stacktrace = thread:status()
스레드 상태를 반환합니다.
출력 :
status
- 문자열 값은 스레드의 상태를 설명합니다. 가능한 값은 "running", "paused", "cancelled", "completed" and "failed"
입니다.err
- 오류 메시지(있는 경우) 이 값은 스레드 상태 == "failed"
인 경우에만 지정됩니다.stacktrace
- 실패한 스레드의 스택 추적입니다. 이 값은 스레드 상태 == "failed"
인 경우에만 지정됩니다.... = thread:get(time, metric)
스레드 완료를 기다리고 함수 결과를 반환하거나 오류가 있는 경우 아무것도 반환하지 않습니다.
input : 시간 측정항목에 따른 작업 시간 초과
출력 : 캡처된 함수 호출의 결과이거나 오류가 있는 경우 아무것도 없습니다.
thread:wait(time, metric)
스레드 완료를 기다리고 스레드 상태를 반환합니다.
input : 시간 측정항목에 따른 작업 시간 초과
출력 : 스레드의 상태를 반환합니다. 출력은 thread:status()
와 동일합니다.
thread:cancel(time, metric)
스레드 실행을 중단합니다. 이 함수가 호출되면 '취소' 플래그가 설정되고 나중에 스레드가 중지될 수 있습니다(이 함수 호출이 완료된 후에도). 스레드가 중지되었는지 확인하려면 무한 시간 초과로 이 함수를 호출하십시오. 완료된 스레드를 취소하면 아무 작업도 수행되지 않고 true
반환됩니다.
input : 시간 측정항목에 따른 작업 시간 초과
출력 : 스레드가 중지된 경우 true
반환하고 false
반환합니다.
thread:pause(time, metric)
스레드를 일시 중지합니다. 이 함수가 호출되면 'pause' 플래그가 설정되고 나중에 스레드가 일시 중지될 수 있습니다(이 함수 호출이 완료된 후에도). 스레드가 일시 중지되었는지 확인하려면 무한 시간 초과로 이 함수를 호출하세요.
input : 시간 측정항목에 따른 작업 시간 초과
출력 : 스레드가 일시 중지된 경우 true
반환하고 false
반환합니다. 스레드가 완료되면 함수는 false
반환합니다.
thread:resume()
일시 중지된 스레드를 재개합니다. 함수는 일시 중지된 경우 즉시 스레드를 재개합니다. 이 함수는 완료된 스레드에 대해서는 아무 작업도 수행하지 않습니다. 함수에는 입력 및 출력 매개변수가 없습니다.
id = effil.thread_id()
고유 식별자를 제공합니다.
출력 : 현재 스레드에 대한 고유 문자열 id
반환합니다.
effil.yield()
명시적인 취소 지점. 함수는 현재 스레드의 취소 또는 일시 중지 플래그를 확인하고 필요한 경우 해당 작업(스레드 취소 또는 일시 중지)을 수행합니다.
effil.sleep(time, metric)
현재 스레드를 일시 중단합니다.
입력 : 시간 측정 항목 인수.
effil.hardware_threads()
구현에서 지원되는 동시 스레드 수를 반환합니다. 기본적으로 std::thread::hardware_concurrency에서 값을 전달합니다.
출력 : 동시 하드웨어 스레드 수입니다.
status, ... = effil.pcall(func, ...)
thread:cancel() 호출로 인한 스레드 취소 오류를 포착하지 않는다는 점을 제외하면 표준 pcall과 정확히 동일한 방식으로 작동합니다.
입력:
산출:
true
, 그렇지 않으면 false
effil.table
은 effil 스레드 간에 데이터를 교환하는 방법입니다. 이는 표준 Lua 테이블과 거의 유사하게 작동합니다. 공유 테이블을 사용한 모든 작업은 스레드로부터 안전합니다. 공유 테이블은 기본 유형(숫자, 부울, 문자열), 함수, 테이블, 가벼운 사용자 데이터 및 파일 기반 사용자 데이터를 저장합니다 . 공유 테이블은 Lua 스레드(코루틴) 또는 임의의 사용자 데이터를 저장하지 않습니다 . 여기에서 공유 테이블 사용 예를 확인하세요.
일반 테이블과 함께 공유 테이블을 사용합니다. 공유 테이블에 일반 테이블을 저장하려는 경우 effil은 암시적으로 원본 테이블을 새 공유 테이블에 덤프합니다. 공유 테이블은 항상 하위 테이블을 공유 테이블로 저장합니다.
함수가 포함된 공유 테이블을 사용하세요. 공유 테이블에 함수를 저장하는 경우 effil은 이 함수를 암시적으로 덤프하고 이를 문자열(및 upvalue)로 저장합니다. 모든 함수의 upvalue는 다음 규칙에 따라 캡처됩니다.
table = effil.table(tbl)
새로운 빈 공유 테이블을 생성합니다.
입력 : tbl
- 선택적 매개변수이며 항목이 공유 테이블에 복사 되는 일반 Lua 테이블만 가능합니다.
출력 : 빈 공유 테이블의 새 인스턴스. tbl
내용에 따라 비어 있을 수도 있고 아닐 수도 있습니다.
table[key] = value
지정된 값으로 테이블의 새 키를 설정합니다.
입력 :
key
- 지원되는 유형의 모든 값입니다. 지원되는 유형 목록 보기value
- 지원되는 유형의 값입니다. 지원되는 유형 목록 보기value = table[key]
지정된 키를 사용하여 테이블에서 값을 가져옵니다.
input : key
- 지원되는 유형의 모든 값입니다. 지원되는 유형 목록 보기
출력 : value
- 지원되는 유형의 값입니다. 지원되는 유형 목록 보기
tbl = effil.setmetatable(tbl, mtbl)
새로운 메타테이블을 공유 테이블로 설정합니다. 표준 setmetatable과 유사합니다.
입력 :
tbl
메타테이블을 설정하려는 공유 테이블이어야 합니다.mtbl
일반 테이블이거나 메타테이블이 될 공유 테이블이어야 합니다. 일반 테이블인 경우 effil은 새 공유 테이블을 생성하고 mtbl
의 모든 필드를 복사합니다. 공유 테이블에서 메타테이블을 삭제하려면 mtbl
nil
로 설정하세요. 출력 : 표준 Lua setmetatable 메소드와 유사한 새로운 메타테이블 값을 사용하여 tbl
반환합니다.
mtbl = effil.getmetatable(tbl)
현재 메타테이블을 반환합니다. 표준 getmetatable과 유사
입력 : tbl
공유 테이블이어야 합니다.
출력 : 지정된 공유 테이블의 메타테이블을 반환합니다. 반환된 테이블의 유형은 항상 effil.table
입니다. 기본 메타테이블은 nil
입니다.
tbl = effil.rawset(tbl, key, value)
메타메서드 __newindex
호출하지 않고 테이블 항목을 설정합니다. 표준 원시 세트와 유사
입력 :
tbl
공유 테이블입니다.key
- 재정의할 테이블의 키입니다. 키는 지원되는 모든 유형이 될 수 있습니다.value
- 설정할 값입니다. 값은 지원되는 모든 유형이 될 수 있습니다. 출력 : 동일한 공유 테이블 tbl
반환합니다.
value = effil.rawget(tbl, key)
메타메서드 __index
호출하지 않고 테이블 값을 가져옵니다. 표준 rawget과 유사
입력 :
tbl
공유 테이블입니다.key
- 특정 값을 받기 위한 테이블의 키입니다. 키는 지원되는 모든 유형이 될 수 있습니다. 출력 : 지정된 key
에 저장된 필수 value
반환합니다.
effil.G
사전 정의된 글로벌 공유 테이블입니다. 이 테이블은 항상 모든 스레드(모든 Lua 상태)에 존재합니다.
effil = require " effil "
function job ()
effil = require " effil "
effil . G . key = " value "
end
effil . thread ( job )(): wait ()
print ( effil . G . key ) -- will print "value"
result = effil.dump(obj)
effil.table
일반 Lua 테이블로 전환합니다.
tbl = effil . table ({})
effil . type ( tbl ) -- 'effil.table'
effil . type ( effil . dump ( tbl )) -- 'table'
effil.channel
은 effil 스레드 간에 데이터를 순차적으로 교환하는 방법입니다. 한 스레드에서 메시지를 푸시하고 다른 스레드에서 메시지를 표시할 수 있습니다. 채널의 메시지 는 지원되는 유형의 값 집합입니다. 채널을 사용한 모든 작업은 스레드로부터 안전합니다. 여기에서 채널 사용 예시를 확인하세요.
channel = effil.channel(capacity)
새 채널을 만듭니다.
입력 : 채널의 선택적 용량 입니다. capacity
0
이거나 nil
이면 채널 크기는 무제한입니다. 기본 용량은 0
입니다.
출력 : 채널의 새 인스턴스를 반환합니다.
pushed = channel:push(...)
메시지를 채널에 푸시합니다.
input : 지원되는 유형의 값 수입니다. 여러 값은 단일 채널 메시지로 간주되므로 채널에 푸시하면 용량이 하나씩 감소합니다.
출력 : pushed
값(-s)이 채널 용량에 맞으면 true
이고, 그렇지 않으면 false
.
... = channel:pop(time, metric)
채널의 팝 메시지입니다. 채널에서 값(-s)을 제거하고 반환합니다. 채널이 비어 있으면 값이 나타날 때까지 기다립니다.
입력 : 시간 측정항목에 따른 대기 시간 초과(채널이 비어 있는 경우에만 사용됨)
출력 : 단일 채널:push() 호출로 푸시된 가변적인 양의 값입니다.
size = channel:size()
채널의 실제 메시지 양을 가져옵니다.
출력 : 채널의 메시지 양입니다.
Effil은 effil.table
및 effil.channel
(및 캡처된 upvalue가 있는 함수)에 대한 사용자 정의 가비지 수집기를 제공합니다. 여러 스레드의 테이블과 채널에 대한 순환 참조를 안전하게 관리할 수 있습니다. 그러나 추가 메모리 사용량이 발생할 수 있습니다. effil.gc
effil 가비지 수집기 구성 방법 세트를 제공합니다. 그러나 일반적으로 구성할 필요는 없습니다.
가비지 수집기는 effil이 새로운 공유 개체(캡처된 upvalue가 있는 테이블, 채널 및 함수)를 생성할 때 작업을 수행합니다. 각 반복 GC는 개체의 양을 확인합니다. 할당된 객체의 양이 많아지면 특정 임계값 GC가 가비지 수집을 시작합니다. 임계값은 previous_count * step
으로 계산됩니다. 여기서 previous_count
- 이전 반복의 개체 양(기본적으로 100 )이고 step
사용자가 지정한 숫자 계수(기본적으로 2.0 )입니다.
예를 들어, GC step
가 2.0
이고 할당된 개체의 양이 120
(이전 GC 반복 후 남음)인 경우 할당된 개체의 양이 240
이 되면 GC는 가비지 수집을 시작합니다.
각 스레드는 자체 가비지 수집기를 갖춘 별도의 Lua 상태로 표시됩니다. 따라서 개체는 결국 삭제됩니다. Effil 객체 자체도 GC에 의해 관리되며 __gc
userdata 메타메서드를 역직렬 변환기 후크로 사용합니다. 객체를 강제로 삭제하려면 다음을 수행하십시오.
collectgarbage()
호출합니다.effil.gc.collect()
호출합니다.effil.gc.collect()
가비지 수집을 강제로 수행하지만 모든 effil 객체의 삭제를 보장하지는 않습니다.
count = effil.gc.count()
할당된 공유 테이블 및 채널 수를 표시합니다.
출력 : 현재 할당된 개체 수를 반환합니다. 최소값은 1이며 effil.G
항상 존재합니다.
old_value = effil.gc.step(new_value)
GC 메모리 단계 승수를 가져오거나 설정합니다. 기본값은 2.0
입니다. GC는 할당된 객체의 양이 step
시간에 따라 증가할 때 수집을 트리거합니다.
input : new_value
는 설정할 단계의 선택적 값입니다. nil
이면 함수는 현재 값을 반환합니다.
출력 : old_value
는 단계의 현재( new_value == nil
인 경우) 또는 이전( new_value ~= nil
인 경우) 값입니다.
effil.gc.pause()
GC를 일시중지합니다. 가비지 수집은 자동으로 수행되지 않습니다. 함수에 입력 이나 출력이 없습니다.
effil.gc.resume()
GC를 재개합니다. 자동 가비지 수집을 활성화합니다.
enabled = effil.gc.enabled()
GC 상태를 가져옵니다.
출력 : 자동 가비지 수집이 활성화되면 true
반환하고, 그렇지 않으면 false
를 반환합니다. 기본적으로 true
반환합니다.
size = effil.size(obj)
Effil 객체의 항목 수를 반환합니다.
입력 : obj
는 공유 테이블 또는 채널입니다.
출력 : 공유 테이블의 항목 수 또는 채널의 메시지 수
type = effil.type(obj)
스레드, 채널 및 테이블은 사용자 데이터입니다. 따라서 type()
모든 유형에 대해 userdata
반환합니다. 유형을 보다 정확하게 감지하려면 effil.type
사용하십시오. 일반 type()
처럼 동작하지만 특정 사용자 데이터를 감지할 수 있습니다.
입력 : obj
모든 유형의 객체입니다.
출력 : 유형의 문자열 이름. obj
가 Effil 객체이면 함수는 effil.table
과 같은 문자열을 반환하고 다른 경우에는 lua_typename 함수의 결과를 반환합니다.
effil . type ( effil . thread ()) == " effil.thread "
effil . type ( effil . table ()) == " effil.table "
effil . type ( effil . channel ()) == " effil.channel "
effil . type ({}) == " table "
effil . type ( 1 ) == " number "