Multithreading - chung-leong/zigar GitHub Wiki
In order to make effective use of threads in a JavaScript-Zig hydrid application, you need basic understanding of JavaScript's execution model.
At the heart of the language engine is the event loop. On each iteration of the loop an item is taken from the head of the event queue and processed. As a result additional items might be added to the queue. These are then in turn processed in subsequent iterations.
JavaScript is single-threaded. The event loop runs in what we refer to as the main thread. In the browser, the main thread is also responsible for the user interface.
Zig functions, as native code or WebAssembly, run in the main thread when imported into JavaScript.
You can spawn addition threads using
std.Thread.spawn()
to avoid
blocking the main thread.
When a Zig thread invokes a JavaScript function (through a function pointer), it posts a call
request in the JavaScript event queue then sleeps until the event loop gets around to processing
it. The thread calls
std.Thread.Futex.wait()
and is later awakened by a call to
std.Thread.Futex.wake()
in the main thread made from JavaScript. Both synchronous and asynchronous Javaqscript functions
are handled in this manner. Synchronous functions awaken the waiting thread upon return while
asynchronous ones do so when one of the callbacks passed to
Promise.then()
is called.
Multithreading in Node.js
Before any call to JavaScript, the event queue needs to be made accessible for other threads. This
is accomplished by calling zigar.thread.use()
. The function uses the NAPI
function
napi_create_threadsafe_function()
to hook into the JavaScript event dispatch mechanism.
While the hook is in place the event loop will not shutdown where the event queue becomes empty,
since at any time new events could be added. To allow for graceful exit the threadsafe hook must
be removed. You do this by calling zigar.thread.end()
. The function uses
napi_release_threadsafe_function()
to undo what use()
has done.
Multithreading in WebAssembly
The Zig compiler currently generates code conforming to the wasi-thread specification, under which threads are Web Workers. Each worker runs the same WASM executable, with each instance attached to the same shared memory.
Shared memory is a restricted feature in the web browser. It's only available when the web page is cross-origin isolated, a level of protection activated by specific HTTP headers. This means support for multithreading is not a given. On web hosts that only offer barebone service like GitHub Pages, it's unavailable.
Shared memory also imposes a hard ceiling on memory footprint. When
multithreaded
is true, the WASM VM's address space is unable to
grow larger than the value specified by maxMemory
. The default is
64MB.
The number of active Zig-to-JavaScript-bridges is
limited when multithreaded
is true. For each function type you can only point to 16 different
JavaScript functions.
The web browser generally doesn't allow synchronous wait in the main thread (due to the
potential impact on the user interface). Functions like
std.Thread.join()
and std.Thread.Futex.wait()
will cause a fault.