Pthreads support — Emscripten 4.0.9-git (dev) documentation (original) (raw)

Note

Browsers that have implemented and enabled SharedArrayBuffer, are gating it behind Cross Origin Opener Policy (COOP) and Cross Origin Embedder Policy (COEP) headers. Pthreads code will not work in deployed environment unless these headers are correctly set. For more information click this

Emscripten has support for multithreading using SharedArrayBuffer in browsers. That API allows sharing memory between the main thread and web workers as well as atomic operations for synchronization, which enables Emscripten to implement support for the Pthreads (POSIX threads) API. This support is considered stable in Emscripten.

Compiling with pthreads enabled

By default, support for pthreads is not enabled. To enable code generation for pthreads, the following command line flags exist:

There should be no other changes required. In C/C++ code, the preprocessor check #ifdef __EMSCRIPTEN_PTHREADS__ can be used to detect whether Emscripten is currently targeting pthreads.

Note

It is not possible to build one binary that would be able to leverage multithreading when available and fall back to single threaded when not. The best you can do is two separate builds, one with and one without threads, and pick between them at runtime.

Additional flags

Note that Emscripten has the--proxy-to-worker linker flag which sounds similar but is unrelated. That flag does not use pthreads or SharedArrayBuffer, and instead uses a plain Web Worker to run your main program (and postMessage to proxy messages back and forth).

Proxying

The Web allows certain operations to only happen from the main browser thread, like interacting with the DOM. As a result, various operations are proxied to the main browser thread if they are called on a background thread. Seebug 3495 for more information and how to try to work around this until then. To check which operations are proxied, you can look for the function’s implementation in the JS library (src/library_*) and see if it is annotated with__proxy: 'sync' or __proxy: 'async'; however, note that the browser itself proxies certain things (like some GL operations), so there is no general way to be safe here (aside from not blocking on the main browser thread).

In addition, Emscripten currently has a simple model of file I/O only happening on the main application thread (as we support JS plugin filesystems, which cannot share memory); this is another set of operations that are proxied.

Proxying can cause problems in certain cases, see the section on blocking below.

Blocking on the main browser thread

Note that in most cases the “main browser thread” is the same as the “main application thread”. The main browser thread is where web pages start to run JavaScript, and where JavaScript can access the DOM (a page can also create a Web Worker, which would no longer be on the main thread). The main application thread is the one on which you started up the application (by loading the main JS file emitted by Emscripten). If you started it on the main browser thread - by it being a normal HTML page - then the two are identical. However, you can also start a multithreaded application in a worker; in that case the main application thread is that worker, and there is no access to the main browser thread.

The Web API for atomics does not allow blocking on the main thread (specifically, Atomics.wait doesn’t work there). Such blocking is necessary in APIs like pthread_join and anything that uses a futex wait under the hood, like usleep(), emscripten_futex_wait(), orpthread_mutex_lock(). To make them work, we use a busy-wait on the main browser thread, which can make the browser tab unresponsive, and also wastes power. (On a pthread, this isn’t a problem as it runs in a Web Worker, where we don’t need to busy-wait.)

Busy-waiting on the main browser thread in general will work despite the downsides just mentioned, for things like waiting on a lightly-contended mutex. However, things like pthread_join and pthread_cond_waitare often intended to block for long periods of time, and if that happens on the main browser thread, and while other threads expect it to respond, it can cause a surprising deadlock. That can happen because of proxying, see the previous section. If the main thread blocks while a worker attempts to proxy to it, a deadlock can occur.

The bottom line is that on the Web it is bad for the main browser thread to wait on anything else. Therefore by default Emscripten warns ifpthread_join and pthread_cond_wait happen on the main browser thread, and will throw an error if ALLOW_BLOCKING_ON_MAIN_THREAD is zero (whose message will point to here).

To avoid these problems, you can use PROXY_TO_PTHREAD, which as mentioned earlier moves your main() function to a pthread, which leaves the main browser thread to focus only on receiving proxied events. This is recommended in general, but may take some porting work, if the application assumed main() was on the main browser thread.

Another option is to replace blocking calls with nonblocking ones. For example you can replace pthread_join with pthread_tryjoin_np. This may require your application to be refactored to use asynchronous events, perhaps throughemscripten_set_main_loop() or ASYNCIFY.

Special considerations

The Emscripten implementation for the pthreads API should follow the POSIX standard closely, but some behavioral differences do exist:

Allocator performance

The default system allocator in Emscripten, dlmalloc, is very efficient in a single-threaded program, but it has a single global lock which means if there is contention on malloc then you can see overhead. You can usemimallocinstead by using -sMALLOC=mimalloc, which is a more sophisticated allocator tuned for multithreaded performance. mimalloc has separate allocation contexts on each thread, allowing performance to scale a lot better undermalloc/free contention.

Note that mimalloc is larger in code size than dlmalloc, and also uses more memory at runtime (so you may need to adjust INITIAL_MEMORY to a higher value), so there are tradeoffs here.

Running code and tests

Any code that is compiled with pthreads support enabled will currently only work in the Firefox Nightly channel, since the SharedArrayBuffer specification is still in an experimental research stage before standardization. There exists two test suites that can be used to verify the behavior of the pthreads API implementation in Emscripten:

Please check these first in case of any issues. Bugs can be reported to the Emscripten bug tracker as usual.