This document discusses shared memory and multithreading in Node.js using worker_threads, SharedArrayBuffer, and Atomics. It introduces concepts like processes, threads, and memory models. It explains the worker_threads API and how to use SharedArrayBuffer and Atomics for shared memory access across threads. Examples are provided for implementing synchronization primitives like mutexes using these APIs. Finally, links are provided to relevant documentation and repositories for further reading.
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Shared memory and multithreading in Node.js - Timur Shemsedinov - JSFest'19
1. Timur Shemsedinov
5-6 APRIL
Introduction to multithreading concurrent programming in
Node.js with worker_threads, SharedArrayBuffer and Atomics
Shared memory and
multithreading in Node.js
2. Shared memory and multithreading in Node.js
Process
- Separate memory
- Separate resources
- Separate environment
Process / Thread
Thread
- Shared memory space
- Shared resources
- Common environment
Memory mapped files
4. process
Shared memory and multithreading in Node.js
process
JavaScript
thread
V8 libuv
node.js
JavaScript
Shared memory and Message passing
thread
V8 libuv
node.js
JavaScript
thread
V8 libuv
node.js
IPC
5. Shared memory and multithreading in Node.js
Node.js: worker_threads
Not supported:
- process.abort()
- process.chdir(name)
- process.initgroups(...)
- trace_events module
- IPC from parent
- process.setegid(id)
- process.seteuid(id)
- process.setgid(id)
- process.setgroups(...)
- process.setuid(id)
6. Shared memory and multithreading in Node.js
Node.js: worker_threads
Read-only in Worker threads:
- process.env
- process.title
- process.umask([mask])
7. Shared memory and multithreading in Node.js
Node.js: worker_threads
Different behavior:
- process.exit([code]) stops thread not process
- process.memoryUsage() rss for entire process
- Each thread has an independent async_hooks
- Signals will not be delivered through process.on
8. Shared memory and multithreading in Node.js
Node.js: worker_threads API
const threads = require('worker_threads');
const { Worker, isMainThread } = threads;
if (isMainThread) {
const worker =
new Worker(__filename, { workerData: {} });
} else {
const { parentPort } = threads;
}
9. Shared memory and multithreading in Node.js
Node.js: MessagePort API
worker.on('message', (...args) => {});
worker.on('error', err => {});
worker.on('exit', code => {});
worker.postMessage('Hello there!');
parentPort.postMessage('Hello there!');
parentPort.on('message', (...args) => {});
10. Shared memory and multithreading in Node.js
class Point {
constructor(buffer, offset) {
this.data = new Int8Array(buffer, offset, 2);
}
get x() { return this.data[0]; }
set x(value) { this.data[0] = value; }
...
Wrap Shared Memory with OOP
11. Shared memory and multithreading in Node.js
const buffer = new SharedArrayBuffer(64);
const point = new Point(buffer, 4);
point.x += 10;
point.y = 7;
const { x } = point;
Wrap Shared Memory with OOP
12. Shared memory and multithreading in Node.js
- Parallel Programming
- Asynchronous Programming
- Actor Model
- Transactional Memory
- Coroutines
Concurrent Computing
20. Shared memory and multithreading in Node.js
- thread safe data structures
- lock-free data structures
- wait-free algorithms
- conflict-free data structures
Parallel Solutions
21. Shared memory and multithreading in Node.js
class Mutex {
constructor(shared, offset = 0)
enter()
leave()
}
https://github.com/HowProgrammingWorks/Mutex
Mutex
22. Shared memory and multithreading in Node.js
mutex.enter();
// do something
// with shared resources
// or data structures
mutex.leave();
Mutex usage
23. Shared memory and multithreading in Node.js
constructor(shared, offset = 0) {
this.lock = new Int32Array(shared, offset, 1);
this.owner = false;
}
const threads = require('worker_threads');
const { workerData } = threads;
const mutex1 = new Mutex(workerData, offset);
Mutex constructor
24. Shared memory and multithreading in Node.js
enter() {
let prev = Atomics.exchange(this.lock, 0, LOCKED);
while (prev !== UNLOCKED) {
Atomics.wait(this.lock, 0, LOCKED);
prev = Atomics.exchange(this.lock, 0, LOCKED);
}
this.owner = true;
} Example: 6-blocking.js
Mutex.enter with Atomics.wait
25. Shared memory and multithreading in Node.js
leave() {
if (!this.owner) return;
Atomics.store(this.lock, 0, UNLOCKED);
Atomics.notify(this.lock, 0, 1);
this.owner = false;
}
Mutex.leave with Atomics.notify
26. Shared memory and multithreading in Node.js
mutex.enter(() => {
// do something
// with shared resources
// or data structures
mutex.leave();
});
Async Mutex usage
27. Shared memory and multithreading in Node.js
await mutex.enter();
// do something
// with shared resources
// or data structures
mutex.leave();
Async Mutex usage
28. Shared memory and multithreading in Node.js
enter() {
return new Promise(resolve => {
while (true) {
let prev = Atomics.exchange(this.lock, 0, LOCKED);
if (prev === UNLOCKED) break;
}
this.owner = true;
resolve();
});
}
Spinlock
29. Shared memory and multithreading in Node.js
enter() {
return new Promise(resolve => {
const tryEnter = () => {
let prev = Atomics.exchange(this.lock, 0, LOCKED);
if (prev === UNLOCKED) {
this.owner = true;
resolve();
} else {
setTimeout(tryEnter, 0);
}
};
tryEnter();
});
}
Spinlock with setTimeout
31. Shared memory and multithreading in Node.js
enter() {
return new Promise(resolve => {
this.resolve = resolve;
this.trying = true;
this.tryEnter();
});
}
Asynchronous Mutex.enter
32. Shared memory and multithreading in Node.js
tryEnter() {
if (!this.resolve) return;
let prev = Atomics.exchange(this.lock, 0, LOCKED);
if (prev === UNLOCKED) {
this.owner = true;
this.trying = false;
this.resolve();
this.resolve = null;
}
}
Asynchronous Mutex.tryEnter
33. Shared memory and multithreading in Node.js
- Low-level structures
e.g. Register, Counter, Buffer, Array, Lists...
- Abstract structures
e.g. Queue, Graph, Polyline, etc.
- Subject-domain classes
e.g. Sensors, Payment, Biometric data, etc.
Thread safe data structures
34. Shared memory and multithreading in Node.js
locks.request('resource', opt, async lock => {
if (lock) {
// critical section for `resource`
// will be released after return
}
});
https://wicg.github.io/web-locks/
Web Locks API
35. Shared memory and multithreading in Node.js
- https://github.com/nodejs/node/issues/22702
Open
- https://github.com/nodejs/node/pull/22719
Closed
Web Locks for Node.js
36. Shared memory and multithreading in Node.js
- Web Locks requires memory + messaging
- Multiple locks.request() calls requires queue
- Mutex.queue: Array<Lock> // resource
- Lock { name, mode, callback } // request
- Need await locks.request()
- Options: { mode, ifAvailable, steal, signal }
Web Locks API Implementation
37. Shared memory and multithreading in Node.js
locks.request('resource', lock => new Promise(
(resolve, reject) => {
// you can store or pass resolve and
// reject here as callbacks
}
));
https://wicg.github.io/web-locks/
Web Locks: Promise or thenable
38. Shared memory and multithreading in Node.js
const controller = new AbortController();
setTimeout(() => controller.abort(), 200);
const { signal } = controller;
locks.request('resource', { signal }, async lock => {
// lock is held
}).catch(err => {
// err is AbortError
});
Web Locks: Abort
39. Shared memory and multithreading in Node.js
- Passing handles between workers
- Native add-ons (with certain conditions)
- Debug
- Experimental
https://github.com/nodejs/node/issues/22940
To be solved
40. Shared memory and multithreading in Node.js
https://github.com/HowProgrammingWorks/Semaphore
https://github.com/HowProgrammingWorks/Mutex
https://github.com/metarhia/metasync
https://wicg.github.io/web-locks
https://nodejs.org/api/worker_threads.html
https://developer.mozilla.org/en-US/docs/Web/
JavaScript/Reference/Global_Objects/Atomics
Links
41. Shared memory and multithreading in Node.js
https://github.com/tshemsedinov
https://www.youtube.com/TimurShemsedinov
timur.shemsedinov@gmail.com
Thanks