worker_threads

azi_zero·2021년 7월 23일
0

leave a mark 🐾

목록 보기
3/6
post-thumbnail

오늘은 분신술을 가능케하는 worker thread에 대해서 공부해보았다.
worker thread라는 키워드에 도달하기까지 하루가 걸렸다.
그 과정에 대해서는 다른 포스트에 worker thread의 이론적인 내용과 함께 적어보겠다.
일단 worker thread를 사용하기 위한 기본적인 코드, 응용 부분을 공부해보았다.

👯‍♀️ worker_threads란?

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');
}

line by line explanation

main thread == parent thread
worker thread == child thread

Worker class

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

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로 부터 메세지를 받는 것을 의미한다.

isMainThread

const {isMainThread} = require('worer_threads')

true if code is not running inside of Worker thread
worker thread는 말그대로 만들어진(파생된?) thread고,
가장 처음 실행되는 thread는 main thread다!

whole code analyze

하나의 코드를 두개의 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 thread 여러개 만들기

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');
}

line by line explanation

multi threads

worker class의 인스턴스들은 독립적이다

const threads = new Set();
threads.add(
	new Worker(__filename, {workerData: ...})
)  

생성된 thread들은 공통되거나 겹칠 필요가 없기 때문에 worker class의 여러 인스턴스들은 set자료 구조에 저장될 수 있다.

workerData option

workerData는 인스턴스가 갖고있는 데이터이다

new Worker(__filename, {workerData: script});

workerData의 값으로는 어떤 것이든지 들어갈 수 있다.
workerData에 값을 주면, thread의 Worker constructor에 전해진다.
postMessage를 할 때 clone된다!


workerData를 불러와서 사용하면 된다.

const {workerData} = require('worker_threads')
const data = workerData;

workerData를 불러오면 현재 thread를 알 수 있는 지표가 된다.

👯‍♀️ worker 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

🤷‍♀️ 더 알아보기

  1. worker thread 장단점
  2. single thread의 장점
  3. worker thread를 어떤 경우에 사용하면 좋을지? CPU intensive의 예시 케이스 찾아보기

reference

https://velog.io/@goblin820/Node.js-workerthreads-module
https://nodejs.org/api/worker_threads.html

profile
잘 하고 싶은 욕심을 가득 갖고 태어남

0개의 댓글