DOCS · CONCEPTS · WORKERS & THREADING

Workers & threading

Two independent knobs: where GDAL runs (main thread or a Web Worker) and how the wasm was built (single- or multi-threaded).

useWorker: keep the UI thread free

boot
const Module = await initCppJs({ useWorker: true });

With useWorker: true the wasm module boots inside a Web Worker and every object you touch (Gdal, datasets, drivers) is an async proxy. Calls hop to the worker, results hop back. A multi-gigabyte reprojection won't drop a frame in your UI. This is how the bundled converter app runs.

Practical consequence: everything is awaitable. Property-like reads are method calls returning promises; batch what you need (the converter caches driver descriptors once at boot for exactly this reason).

Single- vs multi-threaded builds

The wasm ships in two variants: st (single-threaded) and mt (multi-threaded, pthreads via SharedArrayBuffer). The multi-threaded build lets GDAL parallelise internally; the single-threaded build runs anywhere without special headers. Builds exist for browser, Node and edge targets. When threads share the virtual filesystem, OPFS is the safe backing store: its access handles are synchronous and take an exclusive per-file lock, so the platform serialises concurrent access rather than leaving threads to race.

The plugin builds the single-threaded variant by default. To opt into the multi-threaded build, add target: { runtime: "mt" } to your cppjs.config.js:

cppjs.config.js
import wasm from "@gdal3.js/wasm/cppjs.config.mjs";

export default {
  dependencies: [wasm],
  paths: { config: import.meta.url },
  target: { runtime: "mt" },   // default is "st"
};

That compiles the wasm with pthreads. In the browser the multi-threaded build then needs cross-origin isolation (below); React Native needs no headers (pthreads run over JSI); edge runtimes stay single-threaded.

By default the pool is small (cpp.js caps it at about two workers), so GDAL options that take a thread count, like -wo NUM_THREADS=ALL_CPUS, are bounded by it. Raising the pool is a build-level setting, covered in cpp.js's performance guide.

Cross-origin isolation (multi-threaded only)

SharedArrayBuffer requires the document to be cross-origin isolated. To use the multi-threaded build, serve your app with:

response headers
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

The Vite plugin sets these in dev. In production it's a host setting (Netlify _headers, nginx add_header, etc.). The single-threaded build needs none of this; when in doubt, start there.