브라우저가 인식할 수 있는 몇 안되는 언어인 자바스크립트는 놀랍게도 싱글 스레드이다.
싱글 스레드가 뭔지 모른다면 이전글을 참조하길 바란다.
싱글 스레드 이기 때문에 몇가지 한계가 존재한다.
싱글 스레드의 한계
싱글 스레드의 경우 기본적으로 연산량이 많은 작업을 하는 경우, 그 작업이 완료되어야 다른 작업을 수행할 수 있다.
또 setInterval과 같은 함수로 반복 로직을 구현한 경우 브라우저의 정책으로 인해 쓰로틀링이 걸려버릴 수 있다.
예를 들어 브라우저 탭이 비활성화 될 경우 크롬의 경우 시간이 멈추고, 사파리는 시간이 1.5배속으로 흐르는 등... 결과가 전혀 보장되지 않을 수 있다.
쓰로틀링이란?
쓰로틀링이란 브라우저가 자체적으로 판단했을 때 비활성화 된 탭에서 반복적으로 발생하는 이벤트가 있다면 그 이벤트에 delay등을 줘서 결국 해당 이벤트를 무시하고 메모리 자원을 다른 곳에 쓰는 등 메모리를 효율적으로 관리하는 방법이다. 어떻게 보면 브라우저의 입장에서는 이해가 되는 부분이지만 로직을 짜는 입장에서는 답답할 수 밖에 없다. 정말 계속해서 몇초마다 돌아야 하는 기능을 구현을 하게 된다면 이는 큰 문제가 될 것이다.
이를 해결 할 수 있는 게 바로 웹 워커이다.
웹워커란?
웹 워커는 자바스크립트의 메인 스레드가 아닌 브라우저의 백그라운드 스레드에서 돌기 때문에 브라우저 탭이 비활성화 되어도 영향을 받지 않고 멀티 스레딩의 장점을 취할 수 있다.
그렇다면 웹 워커를 당장 도입하면 되는거 아닌가?
웹 워커를 도입할 때 주의해야 하는 이유
웹워커는 장점과 단점이 뚜렷하다. 그래서인지 해외 개발 커뮤니티를 보더라도
웹워커를 정말 필요로 할 때만 쓰는 것을 추천하는 글이 많다.
이유는 이전글에 설명했다싶이 모든 것은 비용이기 때문이다. 메모리는 유한하다.
간단한 로직이라면 싱글 스레드로도 충분한데 굳이 브라우저의 백그라운드 스레드를 사용하는게 배보다 배꼽이 더 클 수 있다는 것이다. 이전 글에서 설명했듯 멀티 스레드를 사용하게 되면 컨텍스트 스위칭이 잦게 발생하면서 오버헤드 비용이 발생할 수 있고 그렇게 되면 성능 문제가 생기기 때문에 주의를 기울여야 한다는 것이다.
하지만 필요한곳에 적절하게 사용한다면 더할나위 없이 좋은 기능이라서 이번 글에서 다뤄보려고 한다.
웹 워커를 사용하여 타이머 기능을 구현하는 방법은 다음과 같다.
웹 워커 사용하기
참고로 웹 워커는 DOM에 직접 접근하지 못하기 때문에 메인 스레드와 서로 메시지를 주고 받아서 통신한다.
postMessage로 메세지를 보내고, onMessage로 메세지를 받는다.
가장 먼저 이걸 이해해야 웹 워커를 이해할 수 있다.
일단 파일을 분리한다.
1) 메인 자바스크립트 파일과
2) 웹 워커 로직이 있는 파일
쉬운 이해를 위해 샘플은 최대한 간단하게 만들었다.
아래와 같이 1초마다 postMessage라는 함수에 날짜 정보를 넣는 함수가 있다고 가정해보자.
웹 워커 로직이 들어가있는 worker.js이다.
onmessage = function (e) {
if (e.data === "start") {
this.intervalId = this.setInterval(() => {
postMessage(new Date)
}, 1000)
}
if (e.data === "stop") {
clearInterval(this.intervalId)
}
}
위 로직이 들어간 worker.js 파일을 메인 자바스크립트 파일에서 호출한다.
테스트 편의를 위해 script를 html에 inline으로 넣었다.
<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
<div id="timer"></div>
<script type="text/javascript">
const myWorker = new Worker('./worker.js');
myWorker.postMessage('start')
const timerEl = document.querySelector('#timer')
myWorker.onmessage = function (e) {
timerEl.innerHTML = e.data
}
</script>
</html>
위 소스를 간략하게 설명하자면,
timer라는 id를 가진 div를 선언하고 worker라는 js파일을 호출해서 myWorker라는 상수에 담아준다.
그리고 myWorker에 'start'라는 메세지를 보낸다. 그러면 worker.js가 post 된 메세지를 onmessage로 받는데
message의 내용이 'start'였으니 분기문을 타고 아래 1초마다 돌아가는 setInterval 함수가 실행되는 것이다.
아무튼 그렇게 1초마다 도는 스코프에서 우리는 return의 의미로 post 를 다시 할 것인데 어떤 것을 return 할 것이냐면 바로
날짜이다. 이렇게 되면 날짜 정보가 1초마다 업데이트되면서 return 된다.
onmessage = function (e) {
if (e.data === "start") {
this.intervalId = this.setInterval(() => {
postMessage(new Date)
}, 1000)
}
if (e.data === "stop") {
clearInterval(this.intervalId)
}
}
return된 데이터는 onmessage로 받아줘야 하며 받은 데이터를 위에 html body에 선언한 div 태그 값으로 넣어준다.
myWorker.onmessage = function (e) {
timerEl.innerHTML = e.data
}
그렇게 되면 날짜 정보가 계속해서 바뀌는 것을 볼 수 있다.
이제 이 타이머는 특수한 경우가 아닌 이상 브라우저의 탭이 비활성화되어도 계속 같은 시간마다 반복적으로 돌 것이다.
같은 원리로 제어할 수 있다. start 대신 stop을 보내면 되지 않을까?
myWorker.postMessage('start') X
myWorker.postMessage('stop') O
그렇게 되면 worker.js에서 if (e.data === "stop") 라는 분기문을 타고 해당 setInterval 함수를 끝낼 수 있다.
onmessage = function (e) {
if (e.data === "start") {
this.intervalId = this.setInterval(() => {
postMessage(new Date)
}, 1000)
}
if (e.data === "stop") {
clearInterval(this.intervalId)
}
}
'개발자 전향 프로젝트' 카테고리의 다른 글
[QueryDSL] Hexadecimal to Decimal Conversion (데이터 포맷 변환) (0) | 2024.05.21 |
---|---|
QueryDSL 다른 조건으로 같은 테이블 여러번 조인하기 (0) | 2024.04.05 |
싱글스레드 vs 멀티스레드 (1) | 2024.02.15 |
React 입문 - React, JSX, Babel, 그리고 Webpack 이해하기 (1) | 2024.02.15 |
Callback에 대한 의문과 Promise 와 Async/Await 를 쓰는 이유 (feat.비동기vs동기) (1) | 2024.01.30 |