웹사이트 검색

웹 작업자로 CPU 바인딩 작업을 처리하는 방법


작성자는 DOnations 프로그램에 쓰기를 선택했습니다.

소개

웹 응용 프로그램 코드가 단일 스레드에서 차례대로 실행되기 때문에 JavaScript는 일반적으로 단일 스레드 언어라고 합니다. 여러 코어가 있는 장치에서 웹 앱에 액세스하는 경우 JavaScript는 하나의 코어만 사용합니다. 작업이 메인 스레드에서 실행되면 모든 후속 작업은 작업이 완료될 때까지 기다려야 합니다. 작업이 오래 걸리면 메인 스레드를 차단하여 나머지 작업이 실행되지 않도록 합니다. 대부분의 차단 작업은 그래픽 처리, 수학적 계산, 비디오 또는 이미지 압축과 같은 CPU 바인딩 작업이라고도 하는 CPU 집약적인 작업인 경향이 있습니다.

CPU 바운드 작업 외에도 논블로킹(non-blocking)인 I/O 바운드 작업도 있습니다. 이러한 I/O 바운드 작업은 운영 체제(OS)에 요청을 보내고 응답을 기다리는 데 대부분의 시간을 소비합니다. 예를 들어 약속이 해결될 때 실행해야 하는 기능을 정의하는 약속에 대한 네트워크 요청이 있습니다. 즉, 운영 체제가 작업을 완료하고 응답을 반환할 때입니다.

반대로 CPU 바인딩된 작업은 OS를 기다리는 I/O 바인딩된 작업처럼 유휴 상태가 아닙니다. CPU 바운드 태스크는 태스크가 완료될 때까지 CPU를 점유하여 프로세스의 메인 스레드를 차단합니다. 프라미스로 래핑하더라도 여전히 메인 스레드를 차단합니다. 또한 사용자는 웹 앱 사용자 인터페이스(UI)가 정지되고 JavaScript를 사용하는 모든 항목이 작동하지 않을 수 있으므로 기본 스레드가 차단된 때를 알 수 있습니다.

이 문제에 대한 해결책으로 브라우저는 Web Workers API를 도입하여 브라우저에서 멀티스레딩 지원을 제공했습니다. Web Workers를 사용하면 CPU 집약적인 작업을 다른 스레드로 오프로드하여 기본 스레드를 해제할 수 있습니다. 기본 스레드는 하나의 장치 코어에서 JavaScript 코드를 실행하고 오프로드된 작업은 다른 코어에서 실행됩니다. 두 스레드는 메시지 전달을 통해 통신하고 데이터를 공유할 수 있습니다.

이 자습서에서는 브라우저에서 기본 스레드를 차단하는 CPU 바인딩 작업을 만들고 웹 앱에 미치는 영향을 관찰합니다. 그런 다음 약속을 사용하여 CPU 바운드 작업을 비차단으로 만들려고 시도하지만 실패합니다. 마지막으로 메인 스레드를 차단하지 않도록 다른 스레드로 CPU 바운드 작업을 오프로드하는 웹 작업자를 생성합니다.

전제 조건

이 자습서를 따르려면 다음이 필요합니다.

  • 최신 웹 브라우저가 설치된 2개 이상의 코어가 있는 시스템
  • Node.js를 설치하고 로컬 개발 환경을 만드는 방법으로 설정할 수 있는 시스템의 로컬 Node.js 환경
  • JavaScript의 이벤트 루프, 콜백, 약속 및 Async/Await 이해를 읽고 배울 수 있는 이벤트 루프, 콜백 및 약속에 대한 지식
  • 또한 JavaScript로 코딩하는 방법에서 찾을 수 있는 HTML, CSS 및 JavaScript에 대한 기본 지식이 필요합니다.

1단계 - 웹 작업자 없이 CPU 바인딩 작업 만들기

이 단계에서는 차단하지 않는 작업뿐만 아니라 차단하는 CPU 바인딩 작업이 있는 웹 앱을 만듭니다. 응용 프로그램에는 세 개의 버튼이 있습니다. 첫 번째 버튼은 약 50억 번 반복되는 for 루프인 차단 작업을 시작합니다. 두 번째 버튼은 웹 페이지의 값을 증가시키고 세 번째 버튼은 웹 앱의 배경색을 변경합니다. 배경 증가 및 변경 버튼은 차단되지 않습니다.

시작하려면 mkdir 명령을 사용하여 프로젝트 디렉토리를 만듭니다.

  1. mkdir workers_demo

cd 명령을 사용하여 디렉토리로 이동합니다.

  1. cd workers_demo

nano 또는 좋아하는 텍스트 편집기를 사용하여 index.html 파일을 만듭니다.

  1. nano index.html

index.html 파일에서 다음 코드를 추가하여 출력을 표시하는 버튼 및 div 요소를 만듭니다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Web Workers</title>
    <link rel="stylesheet" href="main.css" />
  </head>
  <body>
    <div class="wrapper">
      <div class="total-count"></div>
      <div class="buttons">
        <button class="btn btn-blocking" id="blockbtn">Blocking Task</button>
        <button class="btn btn-nonblocking" id="incrementbtn">Increment</button>
        <button class="btn btn-nonblocking" id="changebtn">
          Change Background
        </button>
      </div>
      <div class="output"></div>
    </div>
    <script src="main.js"></script>
  </body>
</html>

head 섹션에서 앱 스타일을 포함할 main.css 스타일시트를 참조합니다. body 태그에서 total-count 클래스로 div 요소를 만듭니다. 버튼이 클릭됩니다. 다음으로 세 개의 button 요소를 자식으로 포함하는 또 다른 div 요소를 만듭니다. 첫 번째 버튼은 CPU를 많이 사용하는 차단 작업을 시작합니다. 두 번째 버튼은 total-count 클래스로 div 요소의 값을 증가시키고 세 번째 버튼은 JavaScript 코드를 트리거하여 배경색을 변경합니다. 이 두 작업은 차단되지 않습니다.

다음 div 요소에는 CPU 집약적인 작업의 출력이 포함되며 마지막으로 body 태그가 끝나기 전에 main.js 를 참조합니다. 파일에 모든 JavaScript 코드가 포함될 것입니다.

요소에 ID와 클래스가 있음을 알 수 있습니다. 이 단계의 뒷부분에서 JavaScript의 요소를 참조하는 데 사용할 것입니다.

이제 파일을 저장하고 종료하십시오.

main.css 파일을 만들고 엽니다.

  1. nano main.css

main.css 파일에서 다음 콘텐츠를 추가하여 요소의 스타일을 지정합니다.

body {
  background: #fff;
  font-size: 16px;
}

.wrapper {
  max-width: 600px;
  margin: 0 auto;
}
.total-count {
  margin-bottom: 34px;
  font-size: 32px;
  text-align: center;
}

.buttons {
  border: 1px solid green;
  padding: 1rem;
  margin-bottom: 16px;
}

.btn {
  border: 0;
  padding: 1rem;
}

.btn-blocking {
  background-color: #f44336;
  color: #fff;
}

#changebtn {
  background-color: #4caf50;
  color: #fff;
}

.buttons는 단색 녹색 테두리와 밝은 패딩으로 정의되지만 차단 작업은 다른 배경색을 사용하는 .btn-blocking 스타일로 추가로 정의됩니다.

파일을 저장하고 닫습니다.

이제 CSS 스타일을 정의했으므로 JavaScript 코드를 작성하여 HTML 요소를 대화식으로 만듭니다. 파일을 저장하고 종료합니다.

편집기에서 main.js 파일을 만들고 엽니다.

  1. nano main.js

main.js 파일에서 다음 코드를 추가하여 DOM 요소를 참조하십시오.

const blockingBtn = document.getElementById("blockbtn");
const incrementBtn = document.getElementById("incrementbtn");
const changeColorBtn = document.getElementById("changebtn");
const output = document.querySelector(".output");
const totalCountEl = document.querySelector(".total-count");

처음 세 줄에서 document 객체의 getElementByID() 메서드를 사용하여 해당 ID로 버튼을 참조합니다. 마지막 두 줄에서 document 개체의 querySelector() 메서드를 사용하여 클래스 이름으로 div 요소를 참조합니다.

다음으로 incrementBtn 버튼을 클릭할 때 div 요소 값을 증가시키는 이벤트 리스너를 정의합니다.

...
totalCountEl.textContent = 0;

incrementBtn.addEventListener("click", function incrementValue() {
  let counter = totalCountEl.textContent;
  counter++;
  totalCountEl.textContent = counter;
});

먼저 totalCountEl 요소의 텍스트 콘텐츠를 0으로 설정합니다. 그런 다음 DOM의 addEventListener() 메서드를 사용하여 이벤트 리스너를 incrementBtn 버튼에 연결합니다. 이 메서드는 수신할 이벤트와 콜백이라는 두 가지 인수를 사용합니다. 여기서 이벤트 리스너는 click 이벤트를 수신하고 click 이벤트가 발생하면 incrementValue() 콜백을 호출합니다.

incrementValue() 콜백에서 DOM에서 totalCountEl 텍스트 콘텐츠 값을 가져와 counter 변수로 설정합니다. 그런 다음 값을 1씩 증가시키고 totalCountEl 요소 텍스트 콘텐츠를 증가된 값으로 설정합니다.

다음으로 다음 코드를 추가하여 changeColorBtn 버튼에 클릭 이벤트를 연결하여 버튼을 클릭할 때 배경색이 임의로 변경되도록 합니다.

...
changeColorBtn.addEventListener("click", function changeBackgroundColor() {
  colors = ["#009688", "#ffc107", "#dadada"];
  const randomIndex = Math.floor(Math.random() * colors.length)
  const randomColor = colors[randomIndex];
  document.body.style.background = randomColor;
});

이전 코드에서는 사용자가 changeColorBtn 버튼을 클릭할 때 changeBackgroundColor 콜백을 실행하는 클릭 이벤트 리스너를 연결했습니다. 콜백에서 colors 변수를 세 가지 HEX 색상 값의 배열로 설정합니다. 그런 다음 Math.random() 메서드를 호출하고 그 결과에 배열 길이 값을 곱하여 0과 배열 길이 3. 그런 다음 무작위 값은 Math.Floor() 메서드를 사용하여 가장 가까운 정수로 반올림되고 randomIndex 변수에 저장됩니다.

그런 다음 임의 인덱스를 사용하여 배열에서 값을 선택한 다음 document 개체의 body.style.background 속성을 해당 색상으로 설정합니다.

비차단 작업을 실행하는 두 개의 버튼을 구현했으므로 CPU 집약적인 작업을 시작하기 위해 나머지 버튼에 이벤트 리스너를 연결합니다. 루프는 50억 번 반복되고 결과를 DOM에 저장합니다.

여전히 main.js 파일에서 다음 코드를 추가하여 차단 작업을 시작하는 버튼에 대한 클릭 이벤트 리스너를 연결합니다.

...
blockingBtn.addEventListener("click", function blockMainThread() {
  let counter = 0;
  for (let i = 0; i < 5_000_000_000; i++) {
    counter++;
  }
  output.textContent = `Result: ${counter}`;
});

이전 코드에서는 blockMainThread() 콜백을 실행하는 클릭 이벤트 리스너를 연결합니다. 함수 내에서 counter0 값으로 설정한 다음 50억 번 반복되는 루프를 만듭니다. 반복할 때마다 counter 값은 1씩 증가합니다. 루프가 끝나면 계산 결과가 output 요소로 설정됩니다.

이제 전체 파일이 다음과 일치합니다.

const blockingBtn = document.getElementById("blockbtn");
const incrementBtn = document.getElementById("incrementbtn");
const changeColorBtn = document.getElementById("changebtn");
const output = document.querySelector(".output");
const totalCountEl = document.querySelector(".total-count");
totalCountEl.textContent = 0;

incrementBtn.addEventListener("click", function incrementValue() {
  let counter = totalCountEl.textContent;
  counter++;
  totalCountEl.textContent = counter;
});

changeColorBtn.addEventListener("click", function changeBackgroundColor() {
  colors = ["#009688", "#ffc107", "#dadada"];
  const randomIndex = Math.floor(Math.random() * colors.length)
  const randomColor = colors[randomIndex];
  document.body.style.background = randomColor;
});

blockingBtn.addEventListener("click", function blockMainThread() {
  let counter = 0;
  for (let i = 0; i < 5_000_000_000; i++) {
    counter++;
  }
  output.textContent = `Result: ${counter}`;
});

코드 입력이 완료되면 파일을 저장하고 종료합니다.

3단계에서 Web Workers 작업을 시작할 때 CORS(Cross-Origin Resource Sharing) 오류를 방지하려면 앱용 웹 서버를 생성해야 합니다. 다음 명령을 실행하여 서버를 생성합니다.

  1. npx serve .

y를 입력하여 확인하면 콘솔에서 Serving! 메시지를 출력하여 서버가 실행 중임을 확인합니다.

Output
┌─────────────────────────────────────────────────────┐ │ │ │ Serving! │ │ │ │ - Local: http://localhost:3000 │ │ - On Your Network: http://your_ip_address:3000 │ │ │ │ Copied local address to clipboard! │ │ │ └─────────────────────────────────────────────────────┘

선호하는 웹 브라우저를 열고 http://localhost:3000/index.html을 방문하십시오.

참고: 원격 서버에서 튜토리얼을 따르는 경우 포트 포워딩을 사용하여 브라우저에서 index.html 파일을 볼 수 있습니다.

현재 터미널에서 다음 명령을 사용하여 웹 서버를 시작합니다.

  1. npx serve .

메시지가 표시되면 y를 입력하여 계속 진행합니다.

콘솔에 다음 오류가 로드될 수 있지만 웹 서버에 액세스하는 기능에 영향을 미치지 않아야 합니다.

Output
ERROR: Cannot copy server address to clipboard: Couldn't find the `xsel` binary and fallback didn't work. On Debian/Ubuntu you can install xsel with : sudo apt install xsel. ┌─────────────────────────────────────────────────────┐ │ │ │ Serving! │ │ │ │ - Local: http://localhost:3000 │ │ - On Your Network: http://your_ip_address:3000 │ │ │ │ Copied local address to clipboard! │ │ │ └─────────────────────────────────────────────────────┘

로컬 컴퓨터에서 두 번째 터미널을 열고 다음 명령을 입력합니다.

  1. ssh -L 3000:localhost:3000 your_non_root_user@your_server_ip

브라우저로 돌아가서 http://localhost:3000/index.html로 이동하여 앱의 홈페이지에 액세스합니다.

페이지가 로드되면 차단 작업, 증분 및 배경 변경 버튼이 있는 홈페이지가 표시됩니다. 카운트를 증가시키는 버튼을 아직 누르지 않았기 때문에 증가 카운터는 0에서 시작합니다.

먼저 증분 버튼을 여러 번 클릭하여 클릭할 때마다 페이지의 숫자를 업데이트합니다.

둘째, 배경 변경 버튼을 몇 번 클릭하여 페이지의 배경색을 변경합니다.

마지막으로 Blocking Task 버튼을 누른 다음 Increment 및 Change Background 버튼을 무작위로 클릭합니다. 페이지가 응답하지 않고 버튼이 작동하지 않습니다. 차단 작업 버튼이 메인 스레드를 차단한 CPU 집약적인 작업을 시작하고 메인 스레드가 해제될 때까지 다른 코드가 실행되지 않기 때문에 이 동결이 발생합니다. 일정 시간이 경과하고 CPU를 많이 사용하는 작업이 완료되면 페이지에 결과: 5000000000이 표시됩니다. 이때 다른 버튼을 클릭하면 다시 작동을 시작합니다.

경험한 바와 같이 차단 작업은 사용자에게 즉시 눈에 띄고 애플리케이션의 사용자 경험에 해를 끼칠 수 있습니다.

이제 기본 스레드를 통해 앱을 정지시키는 차단 작업이 있는 앱을 만들었으므로 약속을 사용하여 CPU 바인딩 작업을 비차단 작업으로 변환합니다.

2단계 - Promise를 사용하여 CPU 바운드 작업 오프로드

I/O 작업을 처리하기 위해 Fetch API 또는 기타 약속 기반 방법을 사용하면 CPU 바인딩 작업을 약속으로 래핑하면 작업이 차단되지 않는다는 잘못된 인상을 줄 수 있습니다. 소개에서 언급했듯이 I/O 작업은 작업이 완료되면 JavaScript 엔진에 알리는 운영 체제에서 처리되기 때문에 비차단 작업입니다. 운영 체제가 I/O 작업을 수행하는 동안 I/O 작업과 관련된 콜백은 큐에서 OS의 응답을 기다립니다. 대기열에서 기다리는 동안 기본 스레드는 모든 후속 작업을 자유롭게 처리할 수 있습니다. OS에서 응답이 오면 콜백은 메인 스레드에서 실행되며 콜백의 병렬 실행은 없습니다.

Promise가 CPU 바인딩된 작업을 차단하지 않게 만들지 않는다는 것을 보여주기 위해 이 단계에서는 CPU 집약적인 작업을 Promise로 래핑합니다.

텍스트 편집기에서 main.js 파일을 엽니다.

  1. nano main.js

main.js 파일에서 강조 표시된 코드를 추가하여 약속에서 CPU 집약적인 작업을 래핑하는 calculateCount() 함수를 만듭니다.

...
function calculateCount() {
  return new Promise((resolve, reject) => {
    let counter = 0;
    for (let i = 0; i < 5_000_000_000; i++) {
      counter++;
    }
    resolve(counter);
  });
}

blockingBtn.addEventListener("click", function blockMainThread(){
  ....
})

calculateCount() 함수는 약속을 반환합니다. 함수에서 resolvereject 매개변수를 허용하는 콜백을 사용하는 new Promise 구문을 사용하여 약속을 초기화합니다. 매개변수는 콜백에서 작업의 성공 또는 실패를 처리합니다. 콜백에는 50억 번 반복되는 CPU 집약적인 루프가 포함되어 있습니다. 루프가 끝나면 결과와 함께 resolve 메서드를 호출합니다.

이제 calculateCount() 함수에 CPU 바인딩 작업이 있으므로 강조 표시된 코드를 제거합니다.

...
blockingBtn.addEventListener("click", function blockMainThread() {
  let counter = 0;
  for (let i = 0; i < 5_000_000_000; i++) {
    counter++;
  }
  output.textContent = `Result: ${counter}`;
});

코드가 제거되면 blockMainThread() 함수에서 calculateCount() 함수를 호출합니다. 함수가 약속을 반환하므로 약속을 사용하려면 async/await 구문이 필요합니다.

강조 표시된 코드를 추가하여 blockMainThread() 함수를 비동기화하고 calculateCount() 함수를 호출합니다.

...
blockingBtn.addEventListener("click", async function blockMainThread() {
  const counter = await calculateCount();
  output.textContent = `Result: ${counter}`;
});

앞의 코드에서 blockMainThread() 함수 앞에 async 키워드를 붙여서 비동기화합니다. 함수 내에서 calculateCount() 함수 앞에 await 키워드를 추가하고 함수를 호출합니다. await 연산자는 약속이 해결될 때까지 기다립니다. 해결되면 counter 변수는 반환된 값으로 설정되고 output div 요소는 CPU 바인딩 작업의 결과로 설정됩니다.

이제 전체 파일이 다음과 일치합니다.

const blockingBtn = document.getElementById("blockbtn");
const incrementBtn = document.getElementById("incrementbtn");
const changeColorBtn = document.getElementById("changebtn");
const output = document.querySelector(".output");
const totalCountEl = document.querySelector(".total-count");
totalCountEl.textContent = 0;

incrementBtn.addEventListener("click", function incrementValue() {
  let counter = totalCountEl.textContent;
  counter++;
  totalCountEl.textContent = counter;
});

changeColorBtn.addEventListener("click", function changeBackgroundColor() {
  colors = ["#009688", "#ffc107", "#dadada"];
  const randomIndex = Math.floor(Math.random() * colors.length)
  const randomColor = colors[randomIndex];
  document.body.style.background = randomColor;
});

function calculateCount() {
  return new Promise((resolve, reject) => {
    let counter = 0;
    for (let i = 0; i < 5_000_000_000; i++) {
      counter++;
    }
    resolve(counter);
  });
}

blockingBtn.addEventListener("click", async function blockMainThread() {
  const counter = await calculateCount();
  output.textContent = `Result: ${counter}`;
});

변경을 마치면 파일을 저장하고 종료합니다.

서버가 계속 실행 중인 상태에서 브라우저에서 http://localhost:3000/index.html을 새로 고칩니다. 증분 및 배경 변경 버튼을 클릭합니다. 그런 다음 차단 작업 버튼을 클릭한 다음 다른 버튼을 클릭합니다. 다른 버튼은 CPU 바운드 작업이 실행 중일 때 여전히 응답하지 않습니다. 이는 CPU 바운드 작업을 약속으로 래핑해도 작업이 차단되지 않는다는 것을 증명합니다.

Promise를 사용하여 CPU 바운드 오프로드를 시도하고 실패를 확인했으므로 이제 Web Workers를 사용하여 CPU 집약적인 작업을 차단하지 않도록 할 것입니다.

3단계 - 웹 작업자를 사용하여 CPU 바운드 작업 오프로드

이 단계에서는 CPU 바인딩 작업을 worker.js 파일로 이동하여 CPU 바인딩 작업을 오프로드하는 전용 작업자를 생성합니다. main.js 파일에서 worker.js 파일의 경로를 사용하여 전용 웹 작업자를 인스턴스화합니다. Web Worker가 초기화되면 CPU 바운드 작업은 별도의 스레드로 오프로드되고 기본 스레드는 나머지 작업을 자유롭게 처리할 수 있습니다.

먼저 worker.js 파일을 만듭니다.

  1. nano worker.js

worker.js 파일에서 다음 코드를 추가하여 파일에 CPU 바운드 작업을 추가합니다.

let counter = 0;
for (let i = 0; i < 5_000_000_000; i++) {
  counter++;
}

앞의 코드 블록에는 지금까지 사용한 CPU 바인딩 작업이 포함되어 있습니다. 이 코드는 이제 별도의 스레드에서 실행됩니다.

기본 스레드가 계산 결과에 액세스할 수 있도록 하려면 Worker 인터페이스의 postMessage() 메서드를 사용하여 데이터가 포함된 메시지를 보내야 합니다.

worker.js 파일에서 강조 표시된 줄을 추가하여 기본 스레드에 데이터를 보냅니다.

let counter = 0;
for (let i = 0; i < 5_000_000_000; i++) {
  counter++;
}
postMessage(counter);

이 줄에서 CPU 바운드 작업 계산 결과를 포함하는 counter 변수와 함께 postMessage() 메서드를 호출합니다.

파일을 저장하고 닫습니다.

이제 CPU 바인딩 작업을 worker.js로 이동했으므로 main.js 파일을 엽니다.

  1. nano main.js

main.js 파일에서 CPU 바인딩 작업을 포함하는 강조 표시된 줄을 제거합니다.

...
function calculateCount() {
  return new Promise((resolve, reject) => {
    let counter = 0;
    for (let i = 0; i < 5_000_000_000; i++) {
      counter++;
    }
    resolve(counter);
  });
}

blockingBtn.addEventListener("click", async function blockMainThread() {
  const counter = await calculateCount();
  output.textContent = `Result: ${counter}`;
});

blockMainThread 콜백에서 강조 표시된 코드를 추가하여 작업자를 초기화하고 작업자 스레드의 메시지를 수신합니다.

blockingBtn.addEventListener("click", function blockMainThread() {
  const worker = new Worker("worker.js");
  worker.onmessage = (msg) => {
    output.textContent = `Result: ${msg.data}`;
  };
});

먼저 이전에 만든 worker.js 파일의 경로를 사용하여 Worker 인스턴스를 만듭니다. 두 번째로 Worker 인터페이스의 onmessage 속성을 worker 스레드에 연결하면 작업자 스레드에서 오는 모든 메시지를 수신합니다. 들어오는 메시지가 있는 경우 message 이벤트가 발생하여 메시지 데이터 msg를 인수로 사용하여 콜백을 호출합니다. 콜백에서 Web Worker로부터 받은 메시지로 output 텍스트 콘텐츠를 수정합니다.

이제 전체 파일이 다음 코드 블록과 일치합니다.

const blockingBtn = document.getElementById("blockbtn");
const incrementBtn = document.getElementById("incrementbtn");
const changeColorBtn = document.getElementById("changebtn");
const output = document.querySelector(".output");
const totalCountEl = document.querySelector(".total-count");
totalCountEl.textContent = 0;

incrementBtn.addEventListener("click", function incrementValue() {
  let counter = totalCountEl.textContent;
  counter++;
  totalCountEl.textContent = counter;
});

changeColorBtn.addEventListener("click", function changeBackgroundColor() {
  colors = ["#009688", "#ffc107", "#dadada"];
  const randomIndex = Math.floor(Math.random() * colors.length)
  const randomColor = colors[randomIndex];
  document.body.style.background = randomColor;
});

blockingBtn.addEventListener("click", function blockMainThread() {
  const worker = new Worker("worker.js");
  worker.onmessage = (msg) => {
    output.textContent = `Result: ${msg.data}`;
  };
});

파일을 저장하고 종료합니다.

서버가 실행 중이면 웹 브라우저로 돌아가서 http://localhost:3000/index.html을 방문하십시오. 페이지가 서버에서 성공적으로 로드됩니다.

먼저 증분 및 배경 변경 버튼을 몇 번 클릭합니다. 둘째, 차단 작업 버튼을 클릭하여 CPU 집약적 작업을 시작한 다음 다른 버튼을 계속 클릭합니다. CPU를 많이 사용하는 작업이 계속 실행 중이더라도 이제 버튼이 문제 없이 작동합니다.

이제 전용 Web Worker를 사용하여 CPU 집약적인 작업을 오프로드하여 차단하지 않도록 할 수 있습니다.

결론

이 자습서에서는 기본 스레드를 차단하는 CPU 바인딩 작업을 시작하는 앱을 만들었습니다. 그런 다음 약속을 사용하여 CPU 바인딩 작업을 차단하지 않도록 만들려고 시도했지만 실패했습니다. 마지막으로 전용 Web Worker를 사용하여 CPU 바운드 작업을 다른 스레드로 오프로드하여 비차단 상태로 만들었습니다.

다음 단계로 오프라인 액세스를 제공하고 성능을 높이는 데 사용할 수 있는 서비스 작업자를 방문할 수 있습니다.

Node.js를 사용하는 경우 Node.js에서 멀티스레딩을 사용하는 방법에서 작업자 스레드를 사용하는 방법을 배울 수 있습니다.