V8 is Google's open source JavaScript engine.
V8 implements ECMAScript as specified in ECMA-262.
V8 is written in C++ and is used in Chromium, the open source browser from Google.
V8 can run standalone, or can be embedded into any C++ application.
V8 Project page: https://v8.dev/docs
Checkout depot tools, and run
fetch v8
This will checkout V8 into the directory v8 and fetch all of its dependencies.
To stay up to date, run
git pull origin
gclient sync
For fetching all branches, add the following into your remote
configuration in .git/config:
fetch = +refs/branch-heads/*:refs/remotes/branch-heads/*
fetch = +refs/tags/*:refs/tags/*
V8 now includes experimental multithreading support that enables true parallel JavaScript execution across multiple OS threads. The design is inspired by Rust's threading model — safe concurrency through ownership and message passing — while staying idiomatic to JavaScript with full async/await integration.
- Work-stealing thread pool: A fixed-size pool of OS threads (defaults to
navigator.hardwareConcurrency). Idle threads steal tasks from busy ones for optimal load balancing. - Isolate-per-pool-thread: Each pool thread owns a pre-warmed V8 Isolate, keeping memory usage fixed and avoiding per-spawn overhead.
- Shared-nothing memory model: Threads do not share mutable state. Data
moves between threads via structured cloning (deep copy), transferables (move
semantics), or
SharedArrayBuffer(zero-copy for advanced use). - Async-first design: Every blocking operation returns a
Promise, so threads integrate naturally withasync/awaitand the event loop.
For a simplified and fully explained guide of the APIs, see the Multithreading API Documentation.
Thread.spawn schedules a function on a pool thread and returns an awaitable
JoinHandle:
// Basic spawn + awaitconsthandle=Thread.spawn(()=>{returnfibonacci(40);});constresult=awaithandle.join();// 102334155// Spawn with arguments (serialized via structured clone)consthandle=Thread.spawn((a,b)=>{returna*b;},6,7);constresult=awaithandle.join();// 42// Async function inside a threadconsthandle=Thread.spawn(async()=>{constdata=awaitfetchData();// each thread has its own event loopreturnprocessData(data);});constresult=awaithandle.join();asyncfunctiondelayedWork(){console.log("Starting...");awaitThread.sleep(1000);// sleeps 1 second, non-blockingconsole.log("Done!");}Channels provide safe cross-thread communication with async/await:
const[tx,rx]=Thread.channel();// Producer threadThread.spawn(async()=>{for(leti=0;i<10;i++){awaittx.send({index: i,value: i*i});awaitThread.sleep(100);}tx.close();// signal no more messages});// Consumer — async iterationforawait(constmsgofrx){console.log(msg);// { index: 0, value: 0 }, ...}// Or receive one at a timeconstmsg=awaitrx.recv();// awaits next messageconstcounter=Thread.mutex(0);// Mutex<number>// Spawn 10 threads, each incrementing the counterconsthandles=Array.from({length: 10},()=>Thread.spawn(async()=>{for(leti=0;i<1000;i++){awaitcounter.lock(value=>value+1);// async lock + transform}}));// Await all threadsawaitPromise.all(handles.map(h=>h.join()));console.log(awaitcounter.value());// 10000 — no data racesThreads are first-class async citizens. Every thread API returns a Promise,
so they compose naturally with existing async patterns:
// Parallel async computationsasyncfunctionprocessAll(items){consthandles=items.map(item=>Thread.spawn(async()=>{constresult=awaitheavyCompute(item);returnresult;}));// Await all results — runs truly in parallel, not just concurrentreturnPromise.all(handles.map(h=>h.join()));}// Try/catch works across threadstry{consthandle=Thread.spawn(()=>{thrownewError("Thread error!");});awaithandle.join();}catch(e){console.error(e.message);// "Thread error!" — propagated}// Race between threadsconstfastest=awaitPromise.race([Thread.spawn(()=>computeRouteA(data)).join(),Thread.spawn(()=>computeRouteB(data)).join(),]);// AbortController integrationconstcontroller=newAbortController();consthandle=Thread.spawn(async(signal)=>{while(!signal.aborted){awaitdoWork();}},{signal: controller.signal});// Later: controller.abort();When you await a thread operation (like handle.join() or tx.send()), it does not block the underlying OS thread. Instead:
- It yields the current V8
Isolateexecution back to the event loop. - The OS thread is immediately freed and returned to the work-stealing pool to execute other pending tasks.
- When the awaited operation completes in the background, your JS task is re-queued and resumes execution.
This means you can spawn 100,000 threads with Thread.spawn and await them all, and it will only ever consume a small number of actual OS threads (equal to your pool size).
When the engine detects independent work, it automatically distributes across the thread pool — no API changes needed:
// Promise.all — independent promises run on separate pool threadsconst[users,orders,analytics]=awaitPromise.all([fetchUsers(),fetchOrders(),computeAnalytics(),]);// Array.parallelMap — data parallelism across threadsconstresults=await[1,2,3,4,5,6,7,8].parallelMap(async(n)=>{returnawaitheavyTransform(n);// each runs on a pool thread});// Array.parallelFilterconstvalid=awaitdata.parallelFilter(async(item)=>{returnawaitexpensiveValidation(item);});// Array.parallelReduce — tree-based parallel reductionconsttotal=awaitnumbers.parallelReduce(async(a,b)=>a+b,0);Because V8 isolates operate on a "shared-nothing" memory model, threads use Structured Cloning to pass data. This perfectly deep-copies plain objects (POJOs), Arrays, and Maps, but strips functions, methods, and prototypes.
If you want to use Object-Oriented Programming across threads, you have two options:
- Rehydration: Send plain object data across the thread boundary, and re-wrap it in a Class instance on the receiving end.
- Shared Memory: Back your Class state with a
SharedArrayBufferso multiple threads can safely mutate the exact same memory in parallel.
classPlayer{constructor(data){Object.assign(this,data);}attack(){console.log(this.name+" attacks!");}}constp=newPlayer({name: "Arthur"});Thread.spawn((rawPlayerData)=>{// Rehydrate the plain object back into a Class instanceconstworkerPlayer=newPlayer(rawPlayerData);workerPlayer.attack();// ✅ Works perfectly!},p);// `p` is sent as a plain object stripped of methodsMultithreading is opt-in. Enable it with the v8_enable_multithreading GN flag:
# Generate build files with multithreading enabled
gn gen out/x64.release --args='v8_enable_multithreading=true'# Build
ninja -C out/x64.release d8
# Run a script using threads
out/x64.release/d8 --enable-multithreading my_script.js| Flag | Default | Description |
|---|---|---|
v8_enable_multithreading | false | Enable the threading runtime and JS API |
v8_thread_pool_size | 0 (auto) | Number of pool threads. 0 = hardware_concurrency |
Tip
Want to test it out? You can use the pre-configured custom Node.js repository ready for testing: shadowofleaf96/custom-node.
This experimental multithreading engine can be embedded directly into Node.js, allowing native multithreading in your Node.js applications.
To build Node.js with V8 multithreading support:
- Clone the Node.js repository (
git clone https://github.com/nodejs/node.git). - Replace the
deps/v8directory in the Node.js source tree with this customized V8 repository. - Configure the Node.js build with the multithreading flag enabled:
# On Windows (requires Visual Studio with C++ Clang Compiler and Rust) .\vcbuild.bat --enable-v8-multithreading # On POSIX (Linux/macOS) ./configure --enable-v8-multithreading make -j8
- This will automatically compile V8's multithreading components and link them into the Node.js binary. The threading APIs (
Thread.spawn,Thread.channel, etc.) will be exposed natively within the Node.js environment.
| Platform | Architecture | Status |
|---|---|---|
| Linux | x64, arm64 | ✅ Supported |
| macOS | x64, arm64 | ✅ Supported |
| Windows | x64 | ✅ Supported |
- Safety by default — No shared mutable state. Data races are impossible
without explicit opt-in (
SharedArrayBuffer). - Zero-cost when unused — Multithreading is behind a build flag. No runtime overhead when disabled.
- Async-native — Every thread operation is a
Promise. No callback hell, no blocking the event loop. - Rust-inspired, JS-idiomatic — Familiar API patterns from Rust's
std::thread,std::sync::mpsc, andstd::sync::Mutex, but adapted for JavaScript's async/await ecosystem.
- Zero-Copy SharedArrayBuffer: Added native
SharedArrayBuffersupport toThread.spawn, allowing threads to instantly share and modify memory in parallel without transfer or copying overhead. - Unified Cross-Thread Stack Traces: Errors thrown inside worker threads automatically capture and append the main thread's caller stack frame (
Thread.spawncall site) for seamless debugging across thread boundaries. - V8 Native Task Integration: Integrated
ThreadPooltask tracking intov8::Isolate::HasPendingBackgroundTasks(), seamlessly supporting top-levelawaitin embedders andd8. - Zero-Copy ArrayBuffer Transfer: Added the ability to transfer
ArrayBufferobjects between threads using the{ transfer: [buffer] }option inThread.spawnandtx.send, eliminating the overhead of copying large memory structures. - Dynamic Pool Sizing: Introduced
Thread.getPoolSize()andThread.setPoolSize(n)builtins to dynamically scale the thread pool up and down. The underlying deque arrays also automatically shrink to reclaim memory when idle. - Bounded Channels:
Thread.channel(capacity)now accepts a capacity limit. Full channels exert back-pressure, pausing thePromiseof the sender instead of infinitely queuing messages in memory. - Lazy Worker Initialization: Worker threads now lazily initialize their
v8::Isolateenvironments and are constrained to strict heap size limits (2MB initial, 16MB maximum), dramatically reducing baseline RAM consumption. - Multithreading Engine Stability Fixes: Resolved isolate mismatch sandbox crashes in cross-thread Channel and Mutex promise resolution, corrected a parameter passing bug in parallel array iteration chunks, and fixed a microtask queue lifecycle task leak in the
ThreadPoolthat causedd8to hang on exit.
- Core Engine: Introduced the Chase-Lev work-stealing thread pool directly into the V8 runtime.
- Thread Control: Added
Thread.spawn,Thread.join, and non-blockingThread.sleep. - Concurrency Primitives: Introduced safe message-passing
Channelsand shared-stateMutexconstructs. - Automatic Parallelism: Added
Array.prototype.parallelMap,Array.prototype.parallelFilter,Array.prototype.parallelReduce, and parallel task execution withinPromise.all().
Please follow the instructions mentioned at v8.dev/docs/contribute.