오늘은 분신술을 가능케하는 worker thread에 대해서 공부해보았다.
worker thread라는 키워드에 도달하기까지 하루가 걸렸다.
그 과정에 대해서는 다른 포스트에 worker thread의 이론적인 내용과 함께 적어보겠다.
일단 worker thread를 사용하기 위한 기본적인 코드, 응용 부분을 공부해보았다.
node에서 multi thread방식을 가능하게 해주는 모듈이다
const {Worker, parentPort, isMainThread} = require('worker_threads');
// mainThread = not Worker thread
if (isMainThread) {
const worker = new Worker(__filename);
worker.on('message', (message) => console.log('from worker :', message));
worker.on('exit', () => console.log('worker exit'));
worker.postMessage('ping');
}
// worker thread
else {
workerThreads.parentPort.on('message', (value) => {
console.log('from parent :', value);
workerThreads.parentPort.postMessage('pong');
workerThreads.parentPort.close();
});
console.log('workerThread logic');
}
main thread == parent thread
worker thread == child thread
Worker 클래스의 인스턴스들은 하나의 thread로서 기능한다.
const {Worker} = require('worker_threads') const worker = new Worker(__filename,option);
__filename 안의 코드를 실행하는 Javascript execution thread를 생성한다.
만약 thread가 Worker이면(Worker class의 인스턴스),
이 thread는 MessagePort로서, parent thread와 communication할 수 있다.
messagePort의 기능들을 사용할 수 있다는 말 = parentPort를 이용하지 않아도 된다는 말!여러가지 option중에서 {workerDate:...}가 가장 잘 쓰인다!
worker thread가 갖고있는 workerData에 접근하려면const data = worker.workerData
를 사용하면 된다!
worker.on과 worker.postMessage는 worker thread와 communication하는 도구이다.
worker.on('message', (message)=>{}); worker.on('error', (error)=>{}); worker.on('exit',(code)=>{});
worker thread로 부터 작업 준비(메세지), 작업 에러, 작업 종료 지시가 오면 실행된다.
worker.postMessage(message)
worker thread로 메세지를 보내는 것을 의미한다.
parentPort는 parent thread와 communication하는 도구이다.
const {parentPort} = require('worker_threads')
parentPort는 messagePort의 인스턴스이다.
parentPort는 from/to parent thread의 중간자라고 보면 된다.
parentPort.postMessage('pong');
parent thread로 메세지를 보내는 것을 의미한다.
parentPort.on('message',()=>{})
parent thread로 부터 메세지를 받는 것을 의미한다.
const {isMainThread} = require('worer_threads')
true if code is not running inside of Worker thread
worker thread는 말그대로 만들어진(파생된?) thread고,
가장 처음 실행되는 thread는 main thread다!
하나의 코드를 두개의 thread에서 실행한다고 보면 됨!
main thread
> main.js const {Worker, parentPort, isMainThread} = require('worker_threads'); // 1) 여기는 main thread이기 때문에 isMainThread===true if (isMainThread) { // 2) 새로운 Worker thread를 생성함 -> multi thread 시작 // 이 시점부터는 worker thread도 할 일을 하고있게됨. const worker = new Worker(__filename); // worker thread로부터 응답을 받으면 처리함 worker.on('message', (message) => console.log('from worker :', message)); worker.on('exit', () => console.log('worker exit')); // 3) worker thread로 메세지를 보냄 worker.postMessage('ping'); } else { parentPort.on('message', (value) => { console.log('from parent :', value); workerThreads.parentPort.postMessage('pong'); workerThreads.parentPort.close(); }); console.log('workerThread logic'); }
worker thread
> main.js const {Worker, parentPort, isMainThread} = require('worker_threads'); if (isMainThread) { const worker = new Worker(__filename); worker.on('message', (message) => console.log('from worker :', message)); worker.on('exit', () => console.log('worker exit')); worker.postMessage('ping'); } else { // parentPort로부터 메세지가 오면 처리하게 됨 parentPort.on('message', (value) => { console.log('from parent :', value); // parentPort로 메세지를 보내고 parentPort.postMessage('pong'); // parentPort에게 종료한다는 메세지를 보냄 parentPort.close(); }); // worker thread만들어진 후 바로 실행됨 console.log('workerThread logic'); }
result
workerThread logic
from parent : ping
from worker : pong
worker exit
Worker class의 인스턴스를 여러개 만들면 되는데,, 어떻게 관리할까?
const {Worker, workerData, parentPort, isMainThread} = require('worker_threads');
if (isMainThread) {
const threads = new Set();
threads.add(
new Worker(__filename, {
workerData: 'hello world',
})
);
threads.add(
new Worker(__filename, {
workerData: 'hello js',
})
);
for (let worker of threads) {
worker.on('message', (message) =>
console.log('from worker :', message)
);
worker.on('exit', () => {
threads.delete(worker);
if (threads.size === 0) console.log('worker done');
});
}
} else {
const data = workerData;
parentPort.postMessage(data + ' from here');
}
worker class의 인스턴스들은 독립적이다
const threads = new Set(); threads.add( new Worker(__filename, {workerData: ...}) )
생성된 thread들은 공통되거나 겹칠 필요가 없기 때문에 worker class의 여러 인스턴스들은 set자료 구조에 저장될 수 있다.
workerData는 인스턴스가 갖고있는 데이터이다
new Worker(__filename, {workerData: script});
workerData의 값으로는 어떤 것이든지 들어갈 수 있다.
workerData에 값을 주면, thread의 Worker constructor에 전해진다.
postMessage를 할 때 clone된다!
workerData를 불러와서 사용하면 된다.
const {workerData} = require('worker_threads') const data = workerData;
workerData를 불러오면 현재 thread를 알 수 있는 지표가 된다.
여러가지 숫자들을 출력하는 thread
const {
Worker,
workerData,
parentPort,
isMainThread,
} = require('worker_threads');
const makeNumsArr = (start, end) => {
let result = [];
for (let i = start; i < end; i++) {
result.push(i);
}
return result;
};
if (isMainThread) {
//make three threads
const threads = new Set();
for (let i = 0; i < 3; i++) {
threads.add(
new Worker(__filename, {
workerData: {
workerId: `0${i}_worker`,
start: i * 10,
end: (i + 1) * 10,
},
})
);
}
for (let worker of threads) {
worker.on('message', (message) => {
const { workerId, value } = message;
console.log(`0${workerId} gets ${value}`);
});
}
} else {
const { workerId, start, end } = workerData;
const nums = makeNumsArr(start, end);
parentPort.postMessage({ workerId: workerId, value: nums });
}
result
00_worker gets 0,1,2,3,4,5,6,7,8,9
01_worker gets 10,11,12,13,14,15,16,17,18,19
02_worker gets 20,21,22,23,24,25,26,27,28,29
https://velog.io/@goblin820/Node.js-workerthreads-module
https://nodejs.org/api/worker_threads.html