Web worker

fromzoo·2021년 1월 21일
0

Web worker

자바스크립트는 싱글스레드로 동작하지만 웹 워커를 사용하면 브라우저에서 멀티스레드를 활용할 수 있다.

왜 멀티스레드가 필요할까?

싱글스레드 방식에는 한가지 큰 단점이 있다.

연산량이 많은 작업을 하는 경우, 그 작업이 완료되어야 다른 작업을 수행할 수 있다는 것이다.

예를들어, 웹 게임에서 좌표를 계산하는데 3초가 걸리고 계산된 좌표를 받아 DOM에 반영한다고 생각해보자. 좌표를 계산하느라 3초간 DOM업데이트등의 다른 작업을 수행할 수 가 없다. 사용자 입장에서는 3초를 기다려야한다. 좌표를 동시에 여러번 계산해야 하는 경우 더 심각해진다. 좌료를 20번 계산하면 3 * 20 = 60초를 기다려야 하는 셈이다.

3초간의 시간은 어쩔 수 없다 치더라도, 계산하는 동안 UI 클릭 같은 다른 작업은 진행할 수 있어야 원활한 서비스를 제공할 수 있다. 이럴때 워커가 사용된다.

새로운 쓰레드의 워커를 생성해 워커에게 계산을 맡기고, 메인 쓰레드는 다른 작업을 수행한다. 3초 후 워커가 계산된 좌표를 메인스레드에게 보내주면 메인 스레드는 DOM업데이트를 진행하면 된다.

좌표를 동시에 여러번 계산할 경우 더 유용해진다. 좌표를 계산해야 할 때 마다 워커를 생성한 후 각각의 워커에게 계산 하나씩을 맡기면 된다.

단, 싱글 코어 컴퓨터의 경우 거의 의미가 없다. CPU코어가 많은 컴퓨터일 수록 병렬로 처리할 수 있는 것이 많아지므로 유리하다. 머리 수 많은 CPU를 사야한다. 워커를 여러개 써서 멀티스레드처럼 사용할 수는 있지만 각각의 워커는 여전히 싱글스레드라는 것을 기억하자.

워커는 DOM에 직접 접근하지 못하기 때문에 메인스레드와 서로 메세지를 주고 받아서 통신한다. iFrame과 통신하는 것과 API가 매우 유사하다.

워커 활용 예제

<div id="result"></div>  
<button id="btn">run</button>  

<script> 
function sleep(delay) { 
	var start = new Date().getTime(); 
	while (new Date().getTime() < start + delay); 
} 

document.querySelector('#btn').addEventListener('click', function () { 
	sleep(3000); // 3초가 걸림을 표현 
	var div = document.createElement('div'); 
	div.textContent = Math.random(); 
	document.querySelector('#result').appendChild(div); 
}); 
</script>

run버튼을 눌러보면 모든게 멈춰버린다. 계산하는 3초동안 다른 작업을 할 수 없기 때문이다. 이렇게 되면 사용자는 3초동안 아무것도 할 수 없게 되므로, 실제 서비스에서 이런 상황이 발생하지 않게 해야 한다.

위의 상황을 웹 워커를 사용해서 개선해보자.

<div id="result"></div>  
<button id="btn">run</button>  

<script> 
document.querySelector('#btn').addEventListener('click', function () { 
	var worker = new Worker('./worker.js'); 
	
	worker.addEventListener('message', function(e) { 
		var div = document.createElement('div'); 
		
		div.textContent = e.data; // 0.1238917491 
		document.querySelector('#result').appendChild(div); 
		worker.terminate(); 
	}); 
	
	worker.postMessage('일해라 워커!'); 
}); 
</script>

버튼을 누를 때, 직접 계산하는 것이 아니라 워커를 생성하고 워커에게 postMessage로 일하라고 알린다. 워커에 message 에빈트 리스너를 붙이는 것은 워커로부터 결과값을 받기 위함이다.
e.data로 받아올 수 있다. 결과값을 DOM에 반영한 후에는 terminate 메소드로 워커를 종료한다. 워커(woker.js)는 다음과 같다.

function  sleep(delay)  {  
	var start =  new  Date().getTime();  
	while  (new  Date().getTime()  < start + delay);  
} 

self.addEventListener('message',  function(e)  { 
	console.log(e.data);  // 일해라 워커! 
	sleep(3000);  // 3초가 걸림을 표현  
	var coords = Math.random(); 
	console.log(coords); 
	self.postMessage(coords);  
 });

계산하는 로직이 워커로 이동했다. 워커도 postMessage와 message이벤트 리스너를 사용한다. 마스터에게 메시지를 보내고 받는 것이다. 워커는 새로운 스코프를 형성하기 때문에 self라는 키워드로 자기자신과 연결한다. (this도 가능하지만 this는 상황에 따라 값이 달라질 수 있다) postMessage에 넣은 인자가 e.data로 연결된다.

크롬에서는 로컬 파일이 워커를 생성하는 것을 제한하기 때문에 실습하려면 파이어폭스에서 하면 된다. 버튼이 이제 여러번 잘 눌리고, 각각 3초 후에 DOM이 업데잍 되는 것을 확인할 수 있다. 복잡한 연산은 다른 쓰레드가 계산해주고 결과만 나중에 돌려주기 때문에 매우 유용하다.

워커가 다른 워커를 불러올 수도 있다. worker2.js가 다음 내용이라고 해보자.

self.a = 'hello worker'

worker.js에서 worker2의 self를 불러올 수 있다.

self.addEventListener('message',  function(e)  {  
	var a =  'ignored';  
	importScripts('./worker2.js'); 
	console.log(a);  // hello worker!  
});

importScripts 함수로 불러오면 된다. self에 변수를 추가하는 것에 유의하자. 이래야만 다른 워커에서 사용할 수 있다.

워커에는 DOM에 접근하지 못하는 것 외에도 사용할 수 있는 API에 제약이 있다.

대부분의 웹에서는 워커를 쓸 정도로 복잡한 작업을 진행하지 않기 때문에 웹워커를 사용할 일이 드물다. 하지만 빅테이터 처리나 웹게임 등의 경우에는 유용하다. 계산을 워커에 맡기고 메인스레드는 DOM업데이트만 담당하면 된다. 이러한 방식은 Node.js에서도 cluster나 child_process같은 모듈로 사용하곤 한다.

💊 요약

  • 웹 워커란?
    싱글스레드의 경우 연산량이 많은 작업을 할 때 그 작업이 완료되어야 다른 작업을 수행할 수 있다.

예를들어,
좌표를 계산하는 연산를 실행한 뒤 이 결과를 돔에 반영하려고 한다. 이때 싱글스레드의 경우에 연산이 수행되는 동안에는 사용자가 UI클릭처럼 다른 서비스를 진행할 수 없게된다. 이를 해결하기 위해서 웹워커를 사용한다.

  • 계산방법
  1. 새로운 스레드의 워커를 생성해서 계산을 맡긴다.
  2. 메인스레드는 다른 작업을 실행
  3. 워커의 연산 작업이 끝나면 메인스레드에게 보내주고, 메인스레드는 DOM을 업데이트한다.
  • 워커는 dom에 직접 접근하지 못한다.

  • 동시에 여러개의 연산을 하는 좌표일 경우 더 유리하다!
    좌표를 계산할때마다 각각의 웹워커에게 연산을 맡기면 된다.

  • 웹워커를 여러개로 멀티스레드처럼 사용할 수 는 있지만, 웹워커는 연산 하나만 수행할 수 있는 싱글스레드인 것을 기억하자.

  • 웹에서는 웹워커를 쓸 정도로 복잡한 작업을 요구하지 않기 때문에 거의 쓸 일이 없다. 주로 웹게임이나 빅데이터 처리를 할 때 유용하게 쓰인다.

출처

profile
프론트엔드 주니어 개발자 🚀

0개의 댓글