Web Worker API

woolee의 기록보관소·2022년 11월 18일
0

FE개념정리

목록 보기
12/35

JavaScript에서 multi-thread를 구동하는 방법?

자바스크립트는 항상 단일 스레드(Single Thread)로 동작한다. 예를 들어 HTML 페이지에서 스크립트를 실행할 때 스크립트가 완료될 때까지 기다려야 한다. 심지어 setTimeout, setInterval, 그리고 ajax callbacks들조차도 다중 스레드(Multi Thread)처럼 보이지만 실제로는 단일 스레드로 구동된다(event loops 를 사용한다). 일단 실행되면 차례를 기다려야 하는 것이다.

평소에는 단일 스레드로도 충분하지만, 로딩과 실행이 오래 걸리는 집약적인 코드(매우 큰 문자열의 암호화,복호화/복잡한 연산/큰 배열 등등)가 있는 경우 다중 스레드가 필요하다.

이때 Web Worker를 사용하면 자바스크립트에서도 다중 스레드(Multi Thread) 구동이 가능하다.

Web Worker API

Web Worker는 개발자가 다중 스레드 프로세스를 제어할 수 있는 자바스크립트 API이다. 공유 메모리에 대한 동시 접근(다른 스레드에서 값을 읽고 쓰는)을 처리하는 방법을 제공한다.

자바스크립트와 달리 브라우저는 다중 스레드로 동작한다. 네트워크 통신이나 I/O들은 서로 다른 스레드에서 동작한다.

Web Worker를 사용하면 브라우저의 메인 스레드(main thread)와 별개로 동작하는 스레드를 생성할 수 있다. 이렇게 생성한 스레드는 메인 스레드main thread에서의 렌더링 동작을 방해하지 않는다.

간단한 사용 예시

Web Worker를 실행할 수 있는 별도의 자바스크립트 파일(ex. worker.js)과, Web Worker를 생성할 자바스크립트 파일(ex. main.js)이 각각 필요하다.

main thread에서 worker thread를 생성하고 (const myWorker = new Worker('worker.js');), worker thread의 메시지를 받기 위해 onmessage 이벤트 핸들러를 추가한다.

// (main.js)
if (window.Worker) { // Web Worker가 호환되는지 확인 
  const myWorker = new Worker('worker.js');
  myWorker.onmessage = (e) => {
    console.log(e.data); // 'result' 
  };
}

worker thread에서 작업 처리가 완료되면 postMessage()로 데이터를 main thread의 myWorker.onmessage에 전달한다.

// (worker.js)
let data = '';
// ... 무거운 연산 수행 후 
data = 'result';

postMessage(data);

작업이 끝났거나 worker thread가 필요없다면 terminate() 을 호출한다.
myWorker.terminate();

main thread에서 window는 GlobalScope이다.
worker thread에서 window는 WorkerGlobalScope이다. 그러므로 worker thread는 main thread의 window 메서드나 DOM 조작이 불가능하다. worker thread는 Message System을 통해서만 데이터를 주고 받을 수 있다.

main thread에서는 WorkerGlobalScope에 접근할 수 있다.
self.onmessage = () => {};

worker가 더 필요하다면 자식 worker를 생성할 수 있다.

// worker.js
let subWorker = new Worker('sub_worker.js');
subWorker.onmessage = function (e) {
    console.log(e.data);
}
subWorker.postMessage('test');

postMessage 메서드를 이용해 부모나 자식 thread에게 데이터를 전달할 수 있고, onmessage를 이용해 부모나 자식 thread가 전달하는 데이터를 수신할 수 있다.
main thread와 worker thread 간 주고받는 데이터는 공유되지 않고 복사된다. main thread와 worker thread는 동일한 인스턴스를 공유하지 않으므로 결과적으로 양쪽에 복제본이 생성되는 것과 같다.

worker thread는 main thread의 window scope에 접근할 수 없다. 그럼에도 외부 라이브러리를 사용하려면 importScripts()를 사용해 외부 스크립트를 가져올 수 있다.

importScripts();                         /* imports nothing */
importScripts('foo.js');                 /* imports just "foo.js" */
importScripts('foo.js', 'bar.js');       /* imports two scripts */
importScripts('//example.com/hello.js'); /* You can import scripts from other origins */

importScripts로 가져온 스크립트는 해당 worker thread에만 영향을 주며 전역 값 또한 worker thread의 self(WorkerGlobalScope)에 속하게 된다.

worker thread 내에서 에러가 발생하면, main thread까지 전파되지 않는다. 필요하다면 onerror 메서드를 이용해서 ErrorEvent를 수신할 수 있다.

worker thread에서 런타임 오류가 발생하면 onerror 이벤트 핸들러가 호출된다. error 이벤트에는 3가지 영역이 있다.

  • message : 사람이 읽을 수 있는 오류 메시지
  • filename : 오류가 발생한 스크립트 파일 이름
  • lineno : 오류가 발생한 스크립트 파일의 줄 번호

Web Worker 종류 2가지

  • Dedicated Worker : 부모 자식 간 thread 끼리만 메시지 교환 가능
    (const myWorker = new Worker('worker.js');)
  • Shared Worker : 동일한 도메인 내에 존재하는 여러 thread에서 사용 가능하다. Port를 이용해 통신한다.
    (const myWorker = new SharedWorker('worker.js');)
squareNumber.onchange = () => {
  myWorker.port.postMessage([squareNumber.value, squareNumber.value]);
  console.log('Message posted to worker');
}

더 알아보기

실행이 긴 작업을 마주했을 때,
web worker를 사용해 멀티스레드를 사용하는 방법과 이벤트 루프 태스크를 잘게 쪼개서 비동기적으로 적절히 실행시키는 방법 중 어떤 게 낫지?

관련해서 아래 글을 읽어보면 좋을 것 같다.
Don't block the event loop! 매끄러운 경험을 위한 JavaScript 비동기 처리



worker thread도 WorkerGlobalScope에서 main thred의 구분되는 evnet loop를 갖는가?
The JavaScript Event Loop and Web Workers


참고

Web Worker 간단 정리하기

Since JavaScript is single-threaded, how are web workers in HTML5 doing multi-threading?

Multi-Threaded JavaScript with Web Workers

Using Web Workers

profile
https://medium.com/@wooleejaan

0개의 댓글