React

[React] React에서 web-worker 사용하여 병렬 처리 하기

판교너굴맨 2022. 6. 9. 00:39

개요

사내 프로젝트 도중에 서버에서 많은 데이터를 받아와 가공해야 하는 경우가 있었다. 자바스크립트는 싱글 스레드이기 때문에 이 과정에서 화면은 잠시 멈추는 것처럼 버벅이게 된다. 그래서 다른 동작을 할 수 없는데 이때 계산하는 과정을 서버에서 데이터를 받을 때처럼 다른 스레드가 처리해 줬으면 했었고, 검색해보니 web-worker에 대해 알 수 있었다, (구글 최고)

 

간단한 예제를 만들어보자

export default function App(): JSX.Element {
    const onTestStart = () => {
        console.log('start');
        Array(10000000).fill(0);
        console.log('end');
      };
    const onSubTask = () => console.log('click!!')
    
	return (
    	<div>
          <button
            type="button"
            onClick={onTestStart}
            value="start"
            style={{ background: '#4789e4' }}
          >
            테스트 시작
          </button>
          
          <button
            type="button"
            onClick={onSubTask}
            style={{ background: '#47e476' }}
          >
            다른 작업
          </button>
    </div>
    )
}

여기서 테스트 시작 버튼을 클릭하면 콘솔에 'start'가 출력되고, 길이가 50,000,000인 배열이 생성된다. 배열 생성이 완료 되면 'end' 가 출력된다.

배열의 길이를 길게 설정하면 할수록 'end'가 출력되는 시간이 길어진다. 그리고 그 길어진 시간만큼(배열을 다 생성하는 동안) 다른 작업을 할 수 없다. 즉, 위에 다른 작업 버튼을 눌러도 배열이 다 생성되기 전까지는 'Click'이 출력되지 않는다.

위 이미지는 start 출력 후 배열이 생성되는 도중에 다른작업 버튼을 클릭한 결과이다. 싱글 스레드인 자바스크립트는 콜스택에서 배열 생성을 처리하는 동안 onSubTask 함수를 실행할 수 없다. (한가지 일 밖에 할 수 없으니까..)

배열이 모두 생성 되고 나서야 클릭한 횟수만큼 Click이 콘솔에 출력되었다.

 

나는 배열이 생성 되는 도중에 다른 작업 버튼을 클릭해서 'Click'을 출력하고 싶다. 즉, start와 end 사이에 Click 콘솔을 마구 출력하고 싶었다!

이때 멀티스레드를 사용하기 위해서는 웹 워커 (Web worker)를 사용하면 된다.

Web-Worker

웹 워커
(Web worker)는 스크립트 연산을 웹 어플리케이션의 주 실행 스레드와 분리된 별도의 백그라운드 스레드에서 실행할 수 있는 기술입니다. 웹 워커를 통해 무거운 작업을 분리된 스레드에서 처리하면 주 스레드(보통 UI 스레드)가 멈추거나 느려지지 않고 동작할 수 있습니다.

가장 먼저 worker 스레드에서 실행할 코드를 작성한다.

web_worker.ts

const workerCode = () => {
  postMessage('start');
  Array(50000000).fill(0);
  postMessage('end');
};

let code = workerCode.toString();
code = code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'));

const blob = new Blob([code], { type: 'application/javascript' });
const worker_script = URL.createObjectURL(blob);

export default worker_script;

실행할 코드를 workerCode 함수에  작성하고 blob으로 변환 후에 bolb을 URL에 넣어 worker_script 변수로 리턴해준다.

 

App.ts

import worker_script from './web_worker';

export default function App(): JSX.Element {
    const onTestStart = () => {
        const worker = new Worker(worker_script);
        worker.onerror = err => console.log('error: ', err);
        worker.onmessage = e => console.log(e.data);
      };
    const onSubTask = () => console.log('click!!')
    
	return (
    	<div>
          <button
            type="button"
            onClick={onTestStart}
            value="start"
            style={{ background: '#4789e4' }}
          >
            테스트 시작
          </button>
          
          <button
            type="button"
            onClick={onSubTask}
            style={{ background: '#47e476' }}
          >
            다른 작업
          </button>
    </div>
    )
}

테스트 시작 버튼을 클릭하면 new Worker에 worker_script를 넣어서 worker 스레드가 생성되고 작업을 진행한다.

web_worker.ts 에서 postMessage에 넣은 값을 worker.onMessage로 받을 수 있다. 

반대로 App.tsx에서 보낸 postMessage를 web_worker.ts 파일에서 self.onMessage 등으로 받을 수 있다.

위와 같이 작성하면 배열이 생성되는 도중에 'Click!'을 출력할 수 있다,

후기

서버에서 많은 양의 데이터를 받아 계산하는 코드가 있을 때 페이지 자체가 멈추는 문제를 web-worker를 통해 해결할 수 있게 되었다. 앞으로도 유용하게 쓰이게 될 것 같다.