Yes, nodejs has real threads too! Many thanks to isolated-vm!
npm i isolated-threads
const { Thread, ThreadPool } = require('isolated-threads');
const context = () => {
const random = () => Math.round(Math.random() * 255);
return ({ w, h, c }) => {
const sharedArray = new SharedArrayBuffer(w * h * c);
const image = new Uint8Array(sharedArray);
console.log('generating noise image with params', { w, h, c });
const stride = w * c;
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
for (let k = 0; k < c; k++) {
image[y * stride + x * c + k] = random();
}
}
}
return sharedArray;
}
};
const t = new Thread(context);
(async () => {
const interval = setInterval(() => console.error('event loop'), 100);
const image = new Uint8Array(await t.run({ w: 3840, h: 2160, c: 3 }));
console.log('image output buffer len', image.length);
clearInterval(interval);
})();
import Thread = require("./src/thread");
const context = () => {
const random = () => Math.round(Math.random() * 255);
return ({ w, h, c }: { w: number, h: number, c: number }): SharedArrayBuffer => {
const sharedArray = new SharedArrayBuffer(w * h * c);
const image = new Uint8Array(sharedArray);
console.log('generating noise image with params', { w, h, c });
const stride = w * c;
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
for (let k = 0; k < c; k++) {
image[y * stride + x * c + k] = random();
}
}
}
return sharedArray;
}
};
// inferred arguments and result from context
const t = new Thread(context);
// or
// enforce arguments and result to context
const t = new Thread<[{ w: number, h: number, c: number }], SharedArrayBuffer>(context);
(async () => {
const interval = setInterval(() => console.error('event loop'), 100);
const shared = await t.run({ w: 3840, h: 2160, c: 3 });
const image = new Uint8Array(shared);
console.log('image output buffer len', image.length);
clearInterval(interval);
})();
context
[Function]- Holds a context with constants and/or functions required
- Returns the function that will be called ar tuntime on a separate thread.
options
[object]memoryLimit
: number - the maximum memory (in MB) at which to trigger an error and disposal (for more info see here)
- Will run the context returned function in a separate thread with the given parameters and the returned promise will resolve with the function's return data.
context
[Function]- Holds a context with constants and/or functions required
- Returns the function that will be called ar tuntime on a separate thread.
poolSize
[number] - the number of threads to be spawnedoptions
[object]memoryLimit
: number - the maximum memory (in MB) at which to trigger an error and disposal (for more info see here)
- Will run the context returned function in a separate thread with the given parameters and the returned promise will resolve with the function's return data.
- Avilable schedulers as of now are only round-robin, more to come.
Since the different thread is executed in an isolated instance of v8
, you will not be able to use Buffer
or require
and you will not have access to any kind of event loop functions, like setTimeout
or setInterval
.
When you are passing data between isolates, the data is serialized and copied between threads (read more here).
If you with to copy a large amount of data, you should use Typed Arrays (like UInt8Array
or Float64Array
) with a SharedArrayBuffer
backend (if available).
In the above example we allocate a w * h * c
sized shared buffer, and then use it as a backend for Uint8Array
.
We do all data manipulation using the uint8
array and then return back the shared one.
Upon receiving the shared array from the thread, we reinterpret it into a uint8
array and extract our data.
To debug other threads, constructor's options
must contains inspector
as a port number and optioally filename
as a file name for the thread. If filename
is not passed, then the file:line
where the thread is spawned will be used.
Upon spawning the thread, you will receive a connection URI in the stderr
stream. Navigate to the URI using Google Chrome or use it to configure your IDE of choice.
As of now, you cannot pass function callback to the isolate. If requested, it can be added, but the callback function will run on the main thread, defeating the purposes of isolating threads.
You cannot pass a shared buffer inside an object. You must pass it in a different argument.
({sharedBuffer, w, h, c}) => sharedBuffer
will not work(sharedBuffer, {w, h, c}) => sharedBuffer
will work({a, b, c}) => ({sum: a+b+c})
will work(...args) => Math.max(...args)
will work