Linux + MacOS | jendela |
---|---|
Effil adalah perpustakaan multithreading untuk Lua. Hal ini memungkinkan untuk menelurkan thread asli dan pertukaran data yang aman. Effil telah dirancang untuk menyediakan API yang jelas dan sederhana untuk pengembang lua.
Effil mendukung lua 5.1, 5.2, 5.3 dan LuaJIT. Memerlukan kepatuhan kompiler C++14. Diuji dengan GCC 4.9+, clang 3.8 dan Visual Studio 2015.
git clone --recursive https://github.com/effil/effil effil
cd effil && mkdir build && cd build
cmake .. && make install
luarocks install effil
Seperti yang Anda ketahui, tidak banyak bahasa skrip dengan dukungan multithreading nyata (Lua/Python/Ruby dan lain-lain memiliki kunci juru bahasa global alias GIL). Effil memecahkan masalah ini dengan menjalankan instance Lua VM independen di thread asli terpisah dan menyediakan komunikasi primitif yang kuat untuk membuat thread dan berbagi data.
Perpustakaan Effil menyediakan tiga abstraksi utama:
effil.thread
- menyediakan API untuk manajemen thread.effil.table
- menyediakan API untuk manajemen tabel. Tabel dapat dibagikan antar thread.effil.channel
- menyediakan wadah First-In-First-Out untuk pertukaran data berurutan.Dan banyak utilitas untuk menangani thread dan tabel juga.
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 ()
Keluaran: 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 ()
Keluaran:
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 )
Keluaran:
first value
100500
1
2
Thread result: 3
Effil memungkinkan untuk mengirimkan data antar thread (status juru bahasa Lua) menggunakan effil.channel
, effil.table
atau secara langsung sebagai parameter effil.thread
.
nil
, boolean
, number
, string
lua_dump
. Nilai atas ditangkap sesuai aturan.lua_iscfunction
mengembalikan nilai true) ditransmisikan hanya dengan sebuah pointer menggunakan lua_tocfunction
(dalam lua_State asli) dan lua_pushcfunction (dalam lua_State baru).lua_iscfunction
mengembalikan nilai true tetapi lua_tocfunction
mengembalikan nullptr. Karena itu kami tidak menemukan cara untuk mengirimkannya antara lua_States.effil.table
secara rekursif. Jadi, setiap tabel Lua menjadi effil.table
. Serialisasi tabel mungkin memerlukan banyak waktu untuk tabel besar. Oleh karena itu, lebih baik memasukkan data langsung ke effil.table
untuk menghindari serialisasi tabel. Mari kita perhatikan 2 contoh: -- 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
Pada contoh #1 kita membuat tabel biasa, mengisinya dan mengubahnya menjadi effil.table
. Dalam hal ini Effil perlu memeriksa semua kolom tabel sekali lagi. Cara lainnya adalah contoh #2 dimana pertama kita membuat effil.table
dan setelah itu kita langsung memasukkan data ke effil.table
. Cara ke-2 lebih cepat coba ikuti prinsip ini.
Semua operasi yang menggunakan metrik waktu dapat diblokir atau tidak diblokir dan menggunakan API berikut: (time, metric)
dengan metric
adalah interval waktu seperti 's'
(detik) dan time
adalah jumlah interval.
Contoh:
thread:get()
- menunggu tanpa henti hingga thread selesai.thread:get(0)
- dapatkan tanpa pemblokiran, cukup periksa apakah thread sudah selesai dan kembalithread:get(50, "ms")
- pemblokiran menunggu selama 50 milidetik.Daftar interval waktu yang tersedia:
ms
- milidetik;s
- detik (default);m
- menit;h
- jam.Semua operasi pemblokiran (bahkan dalam mode non-pemblokiran) adalah titik interupsi. Thread yang digantung dalam operasi tersebut dapat diinterupsi dengan menggunakan metode 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
Bekerja dengan fungsi Effil membuat serialisasi dan deserialisasi menggunakan metode lua_dump
dan lua_load
. Nilai up semua fungsi disimpan mengikuti aturan yang sama seperti biasanya. Jika fungsi memiliki nilai upvalue tipe yang tidak didukung, fungsi ini tidak dapat dikirim ke Effil. Anda akan mendapatkan kesalahan dalam hal ini.
Bekerja dengan fungsi Effil dapat menyimpan lingkungan fungsi ( _ENV
) juga. Mengingat lingkungan sebagai tabel biasa, Effil akan menyimpannya dengan cara yang sama seperti tabel lainnya. Tapi tidak masuk akal untuk menyimpan global _G
, jadi ada beberapa yang spesifik:
_ENV ~= _G
). effil.thread
dapat dijeda dan dibatalkan menggunakan metode objek thread yang sesuai thread:cancel()
dan thread:pause()
.
Thread yang Anda coba interupsi dapat diinterupsi dalam dua titik eksekusi: eksplisit dan implisit.
Poin eksplisitnya adalah effil.yield()
local thread = effil . thread ( function ()
while true do
effil . yield ()
end
-- will never reach this line
end )()
thread : cancel ()
Poin implisit adalah pemanggilan kait debug lua yang diatur menggunakan lua_sethook dengan LUA_MASKCOUNT.
Poin implisit bersifat opsional dan diaktifkan hanya jika 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 ()
Selain itu, thread dapat dibatalkan (tetapi tidak dijeda) dalam operasi tunggu pemblokiran atau non-pemblokiran apa pun.
local channel = effil . channel ()
local thread = effil . thread ( function ()
channel : pop () -- thread hangs waiting infinitely
-- will never reach this line
end )()
thread : cancel ()
Bagaimana cara kerja pembatalan?
Ketika Anda membatalkan thread, ini menghasilkan error
lua dengan pesan "Effil: thread is cancelled"
ketika mencapai titik interupsi. Artinya Anda dapat mengetahui kesalahan ini menggunakan pcall
tetapi thread akan menghasilkan kesalahan baru pada titik interupsi berikutnya.
Jika Anda ingin mengetahui kesalahan Anda sendiri tetapi meneruskan kesalahan pembatalan, Anda dapat menggunakan effil.pcall().
Status thread yang dibatalkan akan sama dengan cancelled
hanya jika thread tersebut selesai dengan kesalahan pembatalan. Artinya jika Anda menangkap kesalahan pembatalan, thread mungkin selesai dengan status completed
atau status failed
jika akan ada kesalahan lain.
effil.thread
adalah cara membuat thread. Thread dapat dihentikan, dijeda, dilanjutkan dan dibatalkan. Semua operasi dengan thread bisa sinkron (dengan batas waktu opsional) atau asinkron. Setiap thread berjalan dengan status luanya sendiri.
Gunakan effil.table
dan effil.channel
untuk mengirimkan data melalui thread. Lihat contoh penggunaan thread di sini.
runner = effil.thread(func)
Membuat pelari thread. Runner memunculkan thread baru untuk setiap pemanggilan.
masukan : fungsi - fungsi Lua
output : runner - objek pelari thread untuk mengkonfigurasi dan menjalankan thread baru
Memungkinkan untuk mengonfigurasi dan menjalankan thread baru.
thread = runner(...)
Jalankan fungsi yang ditangkap dengan argumen tertentu di thread terpisah dan kembalikan pegangan thread.
input : Sejumlah argumen yang diperlukan oleh fungsi yang ditangkap.
keluaran : Objek pegangan benang.
runner.path
Merupakan nilai Lua package.path
untuk keadaan baru. Nilai default mewarisi status induk dari package.path
.
runner.cpath
Merupakan nilai Lua package.cpath
untuk keadaan baru. Nilai default mewarisi status induk dari package.cpath
.
runner.step
Jumlah instruksi lua lua antara titik pembatalan (di mana thread dapat dihentikan atau dijeda). Nilai defaultnya adalah 200. Jika nilainya 0 maka thread hanya menggunakan titik pembatalan eksplisit.
Pegangan utas menyediakan API untuk interaksi dengan utas.
status, err, stacktrace = thread:status()
Mengembalikan status thread.
keluaran :
status
- nilai string menjelaskan status thread. Nilai yang mungkin adalah: "running", "paused", "cancelled", "completed" and "failed"
.err
- pesan kesalahan, jika ada. Nilai ini ditentukan hanya jika thread status == "failed"
.stacktrace
- stacktrace dari thread yang gagal. Nilai ini ditentukan hanya jika thread status == "failed"
.... = thread:get(time, metric)
Menunggu penyelesaian thread dan mengembalikan hasil fungsi atau tidak sama sekali jika terjadi kesalahan.
input : Batas waktu operasi dalam hal metrik waktu
output : Hasil pemanggilan fungsi yang ditangkap atau tidak sama sekali jika terjadi kesalahan.
thread:wait(time, metric)
Menunggu penyelesaian thread dan mengembalikan status thread.
input : Batas waktu operasi dalam hal metrik waktu
output : Mengembalikan status thread. Outputnya sama dengan thread:status()
thread:cancel(time, metric)
Mengganggu eksekusi thread. Setelah fungsi ini dipanggil, tanda 'pembatalan' disetel dan thread dapat dihentikan suatu saat nanti (bahkan setelah pemanggilan fungsi ini selesai). Untuk memastikan bahwa thread dihentikan, aktifkan fungsi ini dengan batas waktu tak terbatas. Pembatalan thread yang sudah selesai tidak akan menghasilkan apa-apa dan mengembalikan true
.
input : Batas waktu operasi dalam hal metrik waktu
output : Mengembalikan true
jika thread dihentikan atau false
.
thread:pause(time, metric)
Menjeda rangkaian pesan. Setelah fungsi ini dipanggil, tanda 'jeda' disetel dan thread dapat dijeda suatu saat nanti (bahkan setelah pemanggilan fungsi ini selesai). Untuk memastikan bahwa thread dijeda, aktifkan fungsi ini dengan batas waktu tak terbatas.
input : Batas waktu operasi dalam hal metrik waktu
output : Mengembalikan true
jika thread dijeda atau false
. Jika thread selesai fungsinya akan mengembalikan false
thread:resume()
Melanjutkan thread yang dijeda. Fungsi segera melanjutkan thread jika dijeda. Fungsi ini tidak melakukan apa pun untuk thread yang telah selesai. Fungsi tidak memiliki parameter input dan output.
id = effil.thread_id()
Memberikan pengidentifikasi unik.
output : mengembalikan id
string unik untuk thread saat ini .
effil.yield()
Poin pembatalan eksplisit. Fungsi memeriksa tanda pembatalan atau jeda dari thread saat ini dan jika diperlukan, fungsi tersebut akan melakukan tindakan yang sesuai (membatalkan atau menjeda thread).
effil.sleep(time, metric)
Tangguhkan thread saat ini.
input : argumen metrik waktu.
effil.hardware_threads()
Mengembalikan jumlah thread bersamaan yang didukung oleh implementasi. Pada dasarnya meneruskan nilai dari std::thread::hardware_concurrency.
output : jumlah thread perangkat keras secara bersamaan.
status, ... = effil.pcall(func, ...)
Bekerja dengan cara yang persis sama seperti pcall standar, hanya saja ia tidak akan menangkap kesalahan pembatalan thread yang disebabkan oleh panggilan thread:cancel().
masukan:
keluaran:
true
jika tidak terjadi kesalahan, false
jika tidak effil.table
adalah cara untuk bertukar data antar thread effil. Berperilaku hampir seperti tabel lua standar. Semua operasi dengan tabel bersama aman untuk thread. Tabel bersama menyimpan tipe primitif (angka, boolean, string), fungsi, tabel, data pengguna ringan, dan data pengguna berbasis effil. Tabel bersama tidak menyimpan thread lua (coroutine) atau data pengguna sembarangan. Lihat contoh penggunaan tabel bersama di sini
Gunakan Tabel bersama dengan tabel biasa . Jika Anda ingin menyimpan tabel biasa di tabel bersama, effil secara implisit akan membuang tabel asal ke tabel bersama yang baru. Tabel bersama selalu menyimpan subtabel sebagai tabel bersama.
Gunakan tabel bersama dengan fungsi . Jika Anda menyimpan fungsi di tabel bersama, effil secara implisit membuang fungsi ini dan menyimpannya sebagai string (dan nilainya naik). Nilai atas semua fungsi akan ditangkap sesuai dengan aturan berikut.
table = effil.table(tbl)
Membuat tabel bersama kosong yang baru.
input : tbl
- adalah parameter opsional , hanya dapat berupa tabel Lua biasa yang entrinya akan disalin ke tabel bersama.
output : contoh baru dari tabel bersama yang kosong. Boleh kosong atau tidak, tergantung tbl
isinya.
table[key] = value
Tetapkan kunci tabel baru dengan nilai yang ditentukan.
masukan :
key
- nilai apa pun dari tipe yang didukung. Lihat daftar tipe yang didukungvalue
- nilai apa pun dari tipe yang didukung. Lihat daftar tipe yang didukungvalue = table[key]
Dapatkan nilai dari tabel dengan kunci yang ditentukan.
input : key
- nilai apa pun dari tipe yang didukung. Lihat daftar tipe yang didukung
output : value
- nilai apa pun dari tipe yang didukung. Lihat daftar tipe yang didukung
tbl = effil.setmetatable(tbl, mtbl)
Menetapkan metatabel baru ke tabel bersama. Mirip dengan setmetatable standar.
masukan :
tbl
harus berupa tabel bersama yang ingin Anda atur metatabelnya.mtbl
harus berupa tabel biasa atau tabel bersama yang akan menjadi metatabel. Jika itu tabel biasa, effil akan membuat tabel bersama baru dan menyalin semua bidang mtbl
. Setel mtbl
sama dengan nil
untuk menghapus metatabel dari tabel bersama. output : baru saja mengembalikan tbl
dengan nilai metatabel baru yang mirip dengan metode setmetatable Lua standar.
mtbl = effil.getmetatable(tbl)
Mengembalikan metatabel saat ini. Mirip dengan getmetatable standar
input : tbl
harus berupa tabel bersama.
output : mengembalikan metatabel dari tabel bersama yang ditentukan. Tabel yang dikembalikan selalu memiliki tipe effil.table
. Metatabel default adalah nil
.
tbl = effil.rawset(tbl, key, value)
Setel entri tabel tanpa menerapkan metametode __newindex
. Mirip dengan rawset standar
masukan :
tbl
adalah tabel bersama.key
- kunci tabel yang akan diganti. Kuncinya bisa berupa jenis apa pun yang didukung.value
- nilai yang akan ditetapkan. Nilainya dapat berupa jenis apa pun yang didukung. output : mengembalikan tabel bersama yang sama tbl
value = effil.rawget(tbl, key)
Mendapatkan nilai tabel tanpa menerapkan metametode __index
. Mirip dengan rawget standar
masukan :
tbl
adalah tabel bersama.key
- kunci tabel untuk menerima nilai tertentu. Kuncinya bisa berupa jenis apa pun yang didukung. output : mengembalikan value
yang diperlukan yang disimpan di bawah key
tertentu
effil.G
Adalah tabel bersama global yang telah ditentukan sebelumnya. Tabel ini selalu ada di thread mana pun (keadaan Lua apa pun).
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)
Memindahkan effil.table
ke dalam tabel Lua biasa.
tbl = effil . table ({})
effil . type ( tbl ) -- 'effil.table'
effil . type ( effil . dump ( tbl )) -- 'table'
effil.channel
adalah cara untuk bertukar data secara berurutan antar thread effil. Hal ini memungkinkan untuk mengirim pesan dari satu thread dan mengeluarkannya dari thread lain. Pesan saluran adalah sekumpulan nilai dari tipe yang didukung. Semua operasi dengan saluran aman untuk thread. Lihat contoh penggunaan saluran di sini
channel = effil.channel(capacity)
Membuat saluran baru.
input : kapasitas saluran opsional. Jika capacity
sama dengan 0
atau nil
, ukuran saluran tidak terbatas. Kapasitas default adalah 0
.
output : mengembalikan contoh saluran baru.
pushed = channel:push(...)
Mendorong pesan ke saluran.
input : sejumlah nilai dari tipe yang didukung. Beberapa nilai dianggap sebagai satu pesan saluran sehingga satu dorongan ke saluran akan mengurangi kapasitas sebanyak satu.
output : pushed
sama dengan true
jika nilai(-s) sesuai dengan kapasitas saluran, false
jika sebaliknya.
... = channel:pop(time, metric)
Pesan pop dari saluran. Menghapus nilai(-s) dari saluran dan mengembalikannya. Jika saluran kosong, tunggu hingga nilai apa pun muncul.
input : waktu tunggu dalam metrik waktu (hanya digunakan jika saluran kosong).
output : jumlah nilai variabel yang didorong oleh satu panggilan saluran:push().
size = channel:size()
Dapatkan jumlah pesan aktual di saluran.
output : jumlah pesan dalam saluran.
Effil menyediakan pengumpul sampah khusus untuk effil.table
dan effil.channel
(dan berfungsi dengan nilai yang diambil). Ini memungkinkan pengelolaan referensi siklik yang aman untuk tabel dan saluran di banyak thread. Namun hal ini dapat menyebabkan penggunaan memori tambahan. effil.gc
menyediakan serangkaian metode konfigurasi pengumpul sampah effil. Namun, biasanya Anda tidak perlu mengkonfigurasinya.
Pengumpul sampah melakukan tugasnya ketika effil membuat objek bersama baru (tabel, saluran, dan fungsi dengan nilai yang ditangkap). Setiap iterasi GC memeriksa jumlah objek. Jika jumlah objek yang dialokasikan menjadi lebih tinggi maka nilai ambang batas spesifik GC mulai mengumpulkan sampah. Nilai ambang batas dihitung sebagai previous_count * step
, di mana previous_count
- jumlah objek pada iterasi sebelumnya ( 100 secara default) dan step
adalah koefisien numerik yang ditentukan oleh pengguna ( 2.0 secara default).
Misalnya: jika step
GC adalah 2.0
dan jumlah objek yang dialokasikan adalah 120
(tersisa setelah iterasi GC sebelumnya) maka GC akan mulai mengumpulkan sampah ketika jumlah objek yang dialokasikan sama dengan 240
.
Setiap thread direpresentasikan sebagai keadaan Lua yang terpisah dengan pengumpul sampahnya sendiri. Dengan demikian, objek pada akhirnya akan dihapus. Objek Effil sendiri juga dikelola oleh GC dan menggunakan metametode data pengguna __gc
sebagai kait deserializer. Untuk memaksa penghapusan objek:
collectgarbage()
standar di semua thread.effil.gc.collect()
di thread mana pun.effil.gc.collect()
Paksa pengumpulan sampah, namun ini tidak menjamin penghapusan semua objek effil.
count = effil.gc.count()
Tampilkan jumlah tabel dan saluran bersama yang dialokasikan.
output : mengembalikan jumlah objek yang dialokasikan saat ini. Nilai minimumnya adalah 1, effil.G
selalu ada.
old_value = effil.gc.step(new_value)
Dapatkan/setel pengganda langkah memori GC. Standarnya adalah 2.0
. GC memicu pengumpulan ketika jumlah objek yang dialokasikan bertambah dalam waktu step
.
input : new_value
adalah nilai opsional dari langkah yang akan ditetapkan. Jika nil
maka fungsi hanya akan mengembalikan nilai saat ini.
keluaran : old_value
adalah nilai langkah saat ini (jika new_value == nil
) atau sebelumnya (jika new_value ~= nil
).
effil.gc.pause()
Jeda GC. Pengumpulan sampah tidak akan dilakukan secara otomatis. Fungsi tidak memiliki input atau output apa pun
effil.gc.resume()
Lanjutkan GC. Aktifkan pengumpulan sampah otomatis.
enabled = effil.gc.enabled()
Dapatkan status GC.
output : mengembalikan true
jika pengumpulan sampah otomatis diaktifkan atau false
jika sebaliknya. Secara default mengembalikan true
.
size = effil.size(obj)
Mengembalikan jumlah entri dalam objek Effil.
input : obj
adalah tabel atau saluran bersama.
output : jumlah entri dalam tabel bersama atau jumlah pesan dalam saluran
type = effil.type(obj)
Utas, saluran, dan tabel adalah data pengguna. Jadi, type()
akan mengembalikan userdata
untuk tipe apa pun. Jika Anda ingin mendeteksi tipe dengan lebih tepat gunakan effil.type
. Ini berperilaku seperti type()
biasa, tetapi dapat mendeteksi data pengguna tertentu.
input : obj
adalah objek jenis apa pun.
output : nama string dari tipe. Jika obj
adalah objek Effil maka fungsi mengembalikan string seperti effil.table
dalam kasus lain ia mengembalikan hasil fungsi 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 "