Timur Shemsedinov
PROFESSIONAL JS CONFERENCE 5-6 APRIL ‘19 KIEV, UKRAINE
Introduction to multithreading concurrent programming in
Node.js with worker_threads, SharedArrayBuffer and Atomics
Shared memory and
multithreading in Node.js
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
Node.js
- child_process
- cluster
- worker_threads
--experimental-worker
- Atomics
- SharedArrayBuffer
Node.js 10.5 ... 11.7
process
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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)
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
Node.js: worker_threads
Read-only in Worker threads:
- process.env
- process.title
- process.umask([mask])
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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;
}
PROFESSIONAL JS CONFERENCE 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) => {});
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
- Parallel Programming
- Asynchronous Programming
- Actor Model
- Transactional Memory
- Coroutines
Concurrent Computing
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
Parallel Programming
- Race condition
- Critical section
- Deadlock
- Livelock
- Starvation
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
Int8Array, Uint8Array,
Int16Array, Uint16Array,
Int32Array, Uint32Array
Atomics and SharedArrayBuffer
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
prev = Atomics.add(array, index, value)
prev = Atomics.sub(array, index, value)
prev = Atomics.and(array, index, value)
prev = Atomics.or(array, index, value)
prev = Atomics.xor(array, index, value)
ES2017 Atomics
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
function add(array, index, value) {
1 const prev = array[index];
2 const sum = prev + value;
3 array[index] = sum;
4 return prev;
}
Atomics.add(array, index, value)
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
stored = Atomics.store(array, index, value)
value = Atomics.load(array, index)
prev = Atomics.exchange(array, index, value)
prev = Atomics.compareExchange(
array, index, expected, replacement
)
ES2017 Atomics (CAS)
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
woken = Atomics.notify(array, index, count)
res = Atomics.wait(array, index, value, [timeout])
res <"ok" | "not-equal" | "timed-out">
array <Int32Array>
ES2017 Atomics (notify/wait)
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
Synchronization primitives
- Semaphore
- BinarySemaphore
- CountingSemaphore
- Condition variable
- SpinLock
- Mutex
- TimedMutex
- SharedMutex
- RecursiveMutex
- Monitor
- Barrier
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
- thread safe data structures
- lock-free data structures
- wait-free algorithms
- conflict-free data structures
Parallel Solutions
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
class Mutex {
constructor(shared, offset = 0)
enter()
leave()
}
https://github.com/HowProgrammingWorks/Mutex
Mutex
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
mutex.enter();
// do something
// with shared resources
// or data structures
mutex.leave();
Mutex usage
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
mutex.enter(() => {
// do something
// with shared resources
// or data structures
mutex.leave();
});
Async Mutex usage
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
await mutex.enter();
// do something
// with shared resources
// or data structures
mutex.leave();
Async Mutex usage
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
constructor(messagePort, shared, offset = 0) {
this.port = messagePort;
this.lock = new Int32Array(shared, offset, 1);
this.owner = false;
this.trying = false;
this.resolve = null;
if (messagePort) {
messagePort.on('message', kind => {
if (kind === 'leave' && this.trying) this.tryEnter();
});
} Example: 8-async.js
}
Asynchronous Mutex
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
enter() {
return new Promise(resolve => {
this.resolve = resolve;
this.trying = true;
this.tryEnter();
});
}
Asynchronous Mutex.enter
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE 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
PROFESSIONAL JS CONFERENCE Shared memory and multithreading in Node.js
https://github.com/tshemsedinov
https://www.youtube.com/TimurShemsedinov
timur.shemsedinov@gmail.com
Thanks

JS Fest 2019. Тимур Шемсединов. Разделяемая память в многопоточном Node.js

  • 1.
    Timur Shemsedinov PROFESSIONAL JSCONFERENCE 5-6 APRIL ‘19 KIEV, UKRAINE Introduction to multithreading concurrent programming in Node.js with worker_threads, SharedArrayBuffer and Atomics Shared memory and multithreading in Node.js
  • 2.
    PROFESSIONAL JS CONFERENCEShared 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
  • 3.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js Node.js - child_process - cluster - worker_threads --experimental-worker - Atomics - SharedArrayBuffer Node.js 10.5 ... 11.7
  • 4.
    process PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js Node.js: worker_threads Read-only in Worker threads: - process.env - process.title - process.umask([mask])
  • 7.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js - Parallel Programming - Asynchronous Programming - Actor Model - Transactional Memory - Coroutines Concurrent Computing
  • 13.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js Parallel Programming - Race condition - Critical section - Deadlock - Livelock - Starvation
  • 14.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js Int8Array, Uint8Array, Int16Array, Uint16Array, Int32Array, Uint32Array Atomics and SharedArrayBuffer
  • 15.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js prev = Atomics.add(array, index, value) prev = Atomics.sub(array, index, value) prev = Atomics.and(array, index, value) prev = Atomics.or(array, index, value) prev = Atomics.xor(array, index, value) ES2017 Atomics
  • 16.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js function add(array, index, value) { 1 const prev = array[index]; 2 const sum = prev + value; 3 array[index] = sum; 4 return prev; } Atomics.add(array, index, value)
  • 17.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js stored = Atomics.store(array, index, value) value = Atomics.load(array, index) prev = Atomics.exchange(array, index, value) prev = Atomics.compareExchange( array, index, expected, replacement ) ES2017 Atomics (CAS)
  • 18.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js woken = Atomics.notify(array, index, count) res = Atomics.wait(array, index, value, [timeout]) res <"ok" | "not-equal" | "timed-out"> array <Int32Array> ES2017 Atomics (notify/wait)
  • 19.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js Synchronization primitives - Semaphore - BinarySemaphore - CountingSemaphore - Condition variable - SpinLock - Mutex - TimedMutex - SharedMutex - RecursiveMutex - Monitor - Barrier
  • 20.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js - thread safe data structures - lock-free data structures - wait-free algorithms - conflict-free data structures Parallel Solutions
  • 21.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js class Mutex { constructor(shared, offset = 0) enter() leave() } https://github.com/HowProgrammingWorks/Mutex Mutex
  • 22.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js mutex.enter(); // do something // with shared resources // or data structures mutex.leave(); Mutex usage
  • 23.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js mutex.enter(() => { // do something // with shared resources // or data structures mutex.leave(); }); Async Mutex usage
  • 27.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js await mutex.enter(); // do something // with shared resources // or data structures mutex.leave(); Async Mutex usage
  • 28.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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
  • 30.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js constructor(messagePort, shared, offset = 0) { this.port = messagePort; this.lock = new Int32Array(shared, offset, 1); this.owner = false; this.trying = false; this.resolve = null; if (messagePort) { messagePort.on('message', kind => { if (kind === 'leave' && this.trying) this.tryEnter(); }); } Example: 8-async.js } Asynchronous Mutex
  • 31.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js enter() { return new Promise(resolve => { this.resolve = resolve; this.trying = true; this.tryEnter(); }); } Asynchronous Mutex.enter
  • 32.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared 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.
    PROFESSIONAL JS CONFERENCEShared memory and multithreading in Node.js https://github.com/tshemsedinov https://www.youtube.com/TimurShemsedinov timur.shemsedinov@gmail.com Thanks