ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] 싱글스레드 자바스크립트 에서의 Web Worker
    front-end/Javascript&Typescript 2023. 7. 13. 14:53

    1. Javascript === Single thread

    웹 워커(Web Worker)는 JavaScript의 싱글 스레드 실행 모델과 연관이 있습니다. JavaScript는 기본적으로 싱글 스레드로 동작하는 프로그래밍 언어입니다. 이는 JavaScript 코드가 단일 스레드에서 순차적으로 실행된다는 것을 의미합니다. 즉, 한 번에 하나의 작업만을 처리할 수 있습니다.

    웹 워커는 JavaScript의 싱글 스레드 제한을 극복하기 위해 도입된 기술입니다. 웹 워커는 메인 스레드와 별도의 스레드에서 실행되기 때문에 JavaScript 엔진은 여러 개의 스레드를 사용하여 병렬로 작업을 수행할 수 있습니다. 이로 인해 웹 페이지의 응답성을 유지하면서 복잡한 계산이나 작업을 비동기적으로 처리할 수 있습니다.

     

    하지만 웹 워커는 여전히 JavaScript의 실행 모델을 따르며, 메인 스레드와 웹 워커 간의 통신은 비동기적으로 이루어집니다. 메인 스레드에서 웹 워커로 메시지를 보내거나 웹 워커에서 메인 스레드로 메시지를 보낼 때, 이러한 통신은 비동기적으로 처리되므로 워커의 작업이 완료될 때까지 기다리지 않고 다른 작업을 수행할 수 있습니다.

     

    따라서 워커를 사용하여 별도의 스레드에서 작업을 수행하더라도 JavaScript 여전히 싱글 스레드 모델을 유지합니다. 워커는 병렬 처리를 위해 추가적인 스레드를 사용하지만, 여전히 JavaScript 코드의 실행은 단일 스레드에서 진행됩니다.

     

    2. 웹 워커 (Web worker )란 ?

    웹 워커(Web Worker)는 JavaScript에서 다중 스레딩을 지원하기 위해 사용되는 기술입니다. 일반적으로 JavaScript는 싱글 스레드 환경에서 실행되지만, 웹 워커를 사용하면 웹 페이지의 주 스레드와 별도로 백그라운드에서 실행되는 추가 스레드를 생성할 수 있습니다. 이를 통해 복잡한 계산이나 긴 작업을 수행하고도 웹 페이지의 응답성을 유지할 수 있습니다.

    워커는 페이지와 독립적으로 실행되며, 메인 스레드와는 별도의 스레드 컨텍스트를 가지고 있습니다. 이는 워커가 메인 스레드와 분리되어 독립적으로 작업할 있도록 합니다. 또한, 워커는 메인 스레드와는 별도의 메모리 공간을 가지고 있으므로 메인 스레드와 데이터를 공유하지 않고도 안전하게 작업할 있습니다.

     

    예시를 보면 ,

    index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
    </head>
    <body>
      <button id="sumButton">Calulate sum</button>
      <button id="bgButton">Change BG color</button>
      <script src="main.js"></script>
    </body>
    </html>

    main.js

    const sumBtn = document.getElementById('sumButton');
    const bgBtn = document.getElementById('bgButton');
    
    sumBtn.addEventListener('click',(event)=>{
      let sum = 0 ;
      for(let i =0; i < 1000000000 ;i++){
        sum += i;
      }
    
      alert(`최종 계산 값은 ${sum}`);
    })
    
    bgBtn.addEventListener('click',(event)=>{
      if(document.body.style.background !== 'green'){
        document.body.style.background = 'green';
      }else{
        document.body.style.background ='blue';
      }
    })

    첫번째는 for loop 을 통해 i를 1000000000 의 total 값을 alert에 띄우는 클릭 이벤트입니다. 1000000000이나 더하니까 오래걸리겠죠?

    그리고 두번째는  body background blue , green 토글 형식으로 바꿔주는 이벤트 리스너입니다.

    (아래 참고)

     

    alert event
    change bgcolor event

     

    JavaScript는 기본적으로 싱글 스레드 언어이며, 코드는 이벤트 루프(Event Loop)를 통해 처리됩니다.

    이벤트 루프는 단일 콜 스택(Call Stack)을 가지고 있으며, 코드의 실행은 순차적으로 처리됩니다. 따라서 한 번에 하나의 작업만을 처리하고, 콜 스택이 비어질 때까지 다음 함수를 실행하지 않습니다.

     

    위의 코드에서 sumBtn 버튼의 클릭 이벤트 핸들러에서는 for 루프를 사용하여 expensive 한 계산을 수행하고 있습니다.

    이 계산은 매우 오랜 시간이 걸리는 작업으로, 이벤트 루프의 다음 이벤트가 처리되기 전까지 해당 핸들러가 실행되는 동안 브라우저는 다른 작업을 수행하지 못합니다.

     

    따라서 bgBtn 버튼의 클릭 이벤트 핸들러를 실행하여 배경색을 변경해도 변경이 즉시 반영되지 않습니다.

    이러한 이유로, sumBtn 버튼의 계산 작업이 완료된 후에야 bgBtn 버튼의 이벤트 핸들러가 실행되고 배경색이 변경됩니다.

     

    싱글 스레드 언어인 JavaScript 실행 모델로 인해 코드의 순차적인 처리가 보장되기 때문입니다.

     

    결론은 i 가 다 더해지고 alert가 뜨기까지 배경화면을 바꾸는 버튼을 클릭을 아무리해도 작동이 안되는것입니다.

     

    그렇다면 i가 다 더하는 계산이 일어나는동안 , 배경화면 바꾸는 버튼을 클릭했을때 , 배경화면이 비동기적으로 바뀌는것을 web worker을 통해 구현해보겠습니다.

     

    웹워커 적용

    worker.js

    worker.js 파일을 하나 생성 해주고 , onmessage 이벤트의 인자로 postMessage 된 data 값을 로그에 출력합니다.

    onmessage = function(msg){
      console.log(msg);
    }

     

    main.js

    const worker = new Worker('worker.js'); //path를 담음.
    const sumBtn = document.getElementById('sumButton');
    const bgBtn = document.getElementById('bgButton');
    
    sumBtn.addEventListener('click',(event)=>{
      // let sum = 0 ;
      // for(let i =0; i < 1000000000 ;i++){
      //   sum += i;
      // }
      // alert(`최종 계산 값은 ${sum}`);
      worker.postMessage('hello worker~!');
    })
    
    bgBtn.addEventListener('click',(event)=>{
      if(document.body.style.background !== 'green'){
        document.body.style.background = 'green';
      }else{
        document.body.style.background ='blue';
      }
    })

    for loop 코드를 잠시 주석 처리 하고 postMessage를 통해 'hello worker~'을 보냅니다.

     

    메인 스레드에서 보낸 postMessage로 받은 msg의 로그를 보면 , data 프로퍼티를 보면 'hello worker~'가 수신 이 잘된걸 볼 수 있습니다.

     

     

    자 이제 for loop 의 오래걸리는 계산을 web worker에 처리하게 두고 배경화면 버튼을 눌러도 for loop 에 expensive 계산하는 동안에도 배경화면을 바꾸는 이벤트를 적용 해보겠습니다.

     

    main.js

    const worker = new Worker('worker.js'); // path를 담음.
    const sumBtn = document.getElementById('sumButton');
    const bgBtn = document.getElementById('bgButton');
    
    sumBtn.addEventListener('click',(event)=>{
      worker.postMessage('hello worker~!');
    })
    
    
    worker.onmessage = function(msg){
      console.log(msg);
    }
    
    bgBtn.addEventListener('click',(event)=>{
      if(document.body.style.background !== 'green'){
        document.body.style.background = 'green';
      }else{
        document.body.style.background ='blue';
      }
    })

     

    worker.js

    onmessage = function(msg){
      let sum = 0 ;
      for(let i =0; i < 10000000000 ;i++){
        sum += i;
      }
    
      postMessage(sum);
    }

     

    worker에서 postMessage로 메인스레드에 sum 값을 보내주고 , 메인스레드에서 onmessage 를 통해 로그에 sum 의 값을 찍어보겠습니다.

     

     

    gif를 보면 , cal sum버튼을 누르면  worker가 무거운 계산작업을 하게 두고 , 계산이 다 될때까지 change Bg color 버튼이 기다리지 않고도 작동이 잘 되는것을 볼수있습니다.

    배경화면이 바뀌다가 오른쪽 로그를 보시면 postMessage로 수신이 된것이 보입니다.

     

    위의 예시를 요약하자면 ,

    1.worker 객체 생성:

    -worker 객체는 웹 워커를 생성하기 위해 사용됩니다. Worker 생성자에는 워커 스크립트 파일의 경로(worker.js)가 전달됩니다.

     

    워커 스크립트(worker.js)에서 onmessage 이벤트 핸들러를 사용하여 메시지를 수신합니다. 워커는 메시지를 받으면 주어진 계산 작업을 수행하고 결과를 메인 스레드로 전달합니다.

     

    2.Worker Script:

    -워커 스크립트(worker.js)에서는 onmessage 이벤트 핸들러를 사용하여 메시지를 수신합니다.

    -Worker는 메시지를 받으면 for 루프를 사용하여 매우 큰 계산 작업을 수행합니다. 이 작업은 메인 스레드를 차단하지 않고 백그라운드에서 비동기적으로 처리됩니다.

    -계산 작업이 완료되면 Worker는 postMessage()를 사용하여 결과를 메인 스레드로 전달합니다.

     

    따라서, 클릭 이벤트가 발생하면 메인 스레드는 워커에게 메시지를 보내고, 워커는 백그라운드에서 계산 작업을 처리하고 결과를 메인 스레드로 전달합니다. 이를 통해 메인 스레드는 계산 작업이 진행되는 동안에도 다른 작업을 처리하고 응답성을 유지할 있습니다.

     

     

    Reference

    https://www.youtube.com/watch?v=Gcp7triXFjg&t=657s 

     

Designed by Tistory.