Wasm Workers API
Wasm Workers API
Wasm Workers API
Quick Example
#include <emscripten/wasm_worker.h>
#include <stdio.h>
void run_in_worker()
{
printf("Hello from wasm worker!\n");
}
int main()
{
emscripten_wasm_worker_t worker = emscripten_malloc_wasm_worker(/*stack size: */1024);
emscripten_wasm_worker_post_function_v(worker, run_in_worker);
}
Build the code by passing the Emscripten flag -sWASM_WORKERS at both compile and link steps.
The example code creates a new Worker on the main browser thread, which shares the same
WebAssembly.Module and WebAssembly.Memory object. Then a postMessage() is passed to
the Worker to ask it to execute the function run_in_worker() to print a string.
To explicitly control the memory allocation placement when creating a worker, use the function
emscripten_create_wasm_worker() .
Introduction
In WebAssembly programs, the Memory object that contains the application state can be
shared across multiple Workers. This enables direct, high performance (and if explicit care is
not taken, racy!) access to synchronously share data state between multiple Workers (shared
state multithreading).
The Pthreads API has a long history with native C programming and the POSIX standard, while
Wasm Workers API is unique to Emscripten compiler only.
These two APIs provide largely the same feature set, but have important differences, which this
documentation seeks to explain to help decide which API one should target.
Pthreads vs Wasm Workers: Which One to Use?
The intended audience and use cases of these two multithreading APIs are slightly different.
The focus on Pthreads API is on portability and cross-platform compatibility. This API is best
used in scenarios where portability is most important, e.g. when a codebase is cross-compiled
to multiple platforms, like building both a native Linux x64 executable and an Emscripten
WebAssembly based web site.
Pthreads API in Emscripten seeks to carefully emulate compatibility and the features that the
native Pthreads platforms already provide. This helps porting large C/C++ codebases over to
WebAssembly.
Wasm Workers API on the other hand seeks to provide a “direct mapping” to the web
multithreading primitives as they exist on the web, and call it a day. If an application is only
developed to target WebAssembly, and portability is not a concern, then using Wasm Workers
can provide great benefits in the form of simpler compiled output, less complexity, smaller code
size and possibly better performance.
However this benefit might not be an obvious win. The Pthreads API was designed to be useful
from the synchronous C/C++ language, whereas Web Workers are designed to be useful from
asynchronous JavaScript. WebAssembly C/C++ programs can find themselves somewhere in
the middle.
Both types of threads have thread-local storage (TLS) support via thread_local (C++11),
_Thread_local (C11) and __thread (GNU11) keywords.
Both types of threads support TLS via explicitly linked in Wasm globals (see
test/wasm_worker/wasm_worker_tls_wasm_assembly.c/.S for example code)
Both types of threads can perform an event-based and an infinite loop programming model.
Both can use EM_ASM and EM_JS API to execute JS code on the calling thread.
Both can call out to JS library functions (linked in with --js-library directive) to execute JS
code on the calling thread.
Neither pthreads nor Wasm Workers can be used in conjunction with -sSINGLE_FILE linker
flag.
Wasm Workers on the other hand do not provide a built-in JS function proxying facility. Proxying
a JS function with Wasm Workers can be done by explicitly passing the address of that function
to the emscripten_wasm_worker_post_function_* API.
If you need to synchronously wait for the posted function to finish from within a Worker, use one
of the emscripten_wasm_worker_*() thread synchronization functions to sleep the calling thread
until the callee has finished the operation.
Note that Wasm Workers cannot
Pthreads have cancellation points
At the expense of performance and code size, pthreads implement a notion of POSIX
cancellation points ( pthread_cancel() , pthread_testcancel() ).
Wasm Workers are more lightweight and performant by not enabling that concept.
Wasm Workers omit this concept, and as result Wasm Workers will always start up
asynchronously. If you need to detect when a Wasm Worker has started up, post a ping-pong
function and reply pair manually between the Worker and its creator. If you need to spin up new
threads quickly, consider managing a pool of Wasm Workers yourself.
On the web, if a Worker spawns a child Worker of its own, it will create a nested Worker
hierarchy that the main thread cannot directly access. To sidestep portability issues stemming
from this kind of topology, pthreads flatten the Worker creation chain under the hood so that
only the main browser thread ever spawns threads.
Wasm Workers do not implement this kind of topology flattening, and creating a Wasm Worker
in a Wasm Worker will produce a nested Worker hierarchy. If you need to create Wasm
Workers from within a Wasm Worker, consider which type of hierarchy you would like, and if
necessary, flatten the hierarchy manually by posting the Worker creation over to the main
thread yourself.
Note that support for nested Workers varies across browsers. As of 02/2022, nested Workers
are not supported in Safari . See here for a polyfill.
Pthreads can use the Wasm Worker synchronization API, but not vice
versa
The startup/execution model of pthreads is to start up executing a given thread entry point
function. When that function exits, the pthread will also (by default) quit, and the Worker hosting
that pthread will return to the Worker pool to wait for another thread to be created on it.
Wasm Workers instead implement the direct web-like model, where a newly created Worker
sits idle in its event loop, waiting for functions to be posted to it. When those functions finish,
the Worker will return to its event loop, waiting to receive more functions (or worker scope web
events) to execute. A Wasm Worker will only quit with a call to
emscripten_terminate_wasm_worker(worker_id) or emscripten_terminate_all_wasm_workers() .
Pthreads allow one to register thread exit handlers via pthread_atexit , which will be called
when the thread quits. Wasm Workers do not have this concept.
In order to enable flexible synchronous execution of code on other threads, and to implement
support APIs for example for MEMFS filesystem and Offscreen Framebuffer (WebGL emulated
from a Worker) features, main browser thread and each pthread have a system-backed “proxy
message queue” to receive messages.
Wasm Workers do not provide this functionality. If needed, such messaging should be
implemented manually by users via regular multithreaded synchronized programming
techniques (mutexes, futexes, semaphores, etc.)
Pthreads synchronize wallclock times
Another portability aiding emulation feature that Pthreads provide is that the time values
returned by emscripten_get_now() are synchronized to a common time base across all threads.
Wasm Workers omit this concept, and it is recommended to use the function
emscripten_performance_now() for high performance timing in a Wasm Worker, and avoid
comparing resulting values across Workers, or manually synchronize them.
The multithreaded input API provided in emscripten/html5.h only works with the pthread API.
When calling any of the functions emscripten_set_*_callback_on_thread() , one can choose the
target pthread to be the recipient of the received events.
With Wasm Workers, if desired, “backproxying” events from the main browser thread to a
Wasm Worker should be implemented manually e.g. by using the
emscripten_wasm_worker_post_function_*() API family.
However note that backproxying input events has a drawback that it prevents security sensitive
operations, like fullscreen requests, pointer locking and audio playback resuming, since
handling the input event is detached from the event callback context executing the initial
operation.
The mutex implementation from pthread_mutex_* has a few different creation options, one
being a “recursive” mutex.
The lock implemented by emscripten_lock_* API is not recursive (and does not provide an
option).
Pthreads also offer a programming guard against a programming error that one thread would
not release a lock that is owned by another thread. emscripten_lock_* API does not track lock
ownership.
Memory requirements
Pthreads have a fixed dependency to dynamic memory allocation, and perform calls to malloc
and free to allocate thread specific data, stacks and TLS slots.
With the exception of the helper function emscripten_malloc_wasm_worker() , Wasm Workers are
not dependent on a dynamic memory allocator. Memory allocation needs are met by the caller
at Worker creation time, and can be statically placed if desired.
Generated code size
The disk size overhead from pthreads is on the order of a few hundred KBs. Wasm Workers
runtime on the other hand is optimized for tiny deployments, just a few hundred bytes on disk.
API Differences
To further understand the different APIs available between Pthreads and Wasm Workers, refer
to the following table.
pthread_kill(code)
Thread stack Specify in pthread_attr_t structure. Manage thread stack area explicit
emscripten_create_wasm_worke
functions, or
automatically allocate stack with
emscripten_malloc_wasm_worke
API.
functions, or
automatically via
emscripten_malloc_wasm_worke
API.
Thread ID Creating a pthread obtains its ID. Call Creating a Worker obtains its ID. C
pthread_self() emscripten_wasm_worker_self_
Synchronous Synchronization primitives internally fall back Explicit spin vs sleep synchronizat
blocking on to busy spin loops.
main thread
Futex API
emscripten_futex_wait emscripten_wasm_wait_i32
emscripten_futex_wake emscripten_wasm_wait_i64
in emscripten/threading.h emscripten_wasm_notify
in emscripten/wasm_workers.h
Asynchronous N/A
futex wait emscripten_atomic_wait_asyn
emscripten_*_async_acquire(
Build flags Compile and link with -pthread Compile and link with -sWASM_W
Atomics API Supported, use any of __atomic_* API, __sync_* API or C++11 std::atomic API.
Nonrecursive
mutex pthread_mutex_* emscripten_lock_*
Recursive N/A
mutex pthread_mutex_*
Semaphores N/A
emscripten_semaphore_*
Condition
Variables pthread_cond_* emscripten_condvar_*
Read-Write N/A
locks pthread_rwlock_*
Spinlocks
pthread_spin_* emscripten_lock_busyspin*
WebGL
Offscreen Supported with -sOFFSCREEN_FRAMEBUFFER Not supported.
Framebuffer
Limitations
The following build options are not supported at the moment with Wasm Workers:
-sSINGLE_FILE
-sPROXY_TO_WORKER
-sPROXY_TO_PTHREAD
Example Code
See the directory test/wasm_workers/ for code examples on different Wasm Workers API
functionality.
Previous Next
Report Bug Licensing Contributing Mailing list Wiki Release notes Blogs Contact