[HTML5] WebWorkers

HTML/CSS 2010. 12. 29. 17:16



(WebWorker는 아직 드래프트 문서밖에 없으므로 추후 어떻게 바뀔지는 모름)
드래프트 문서: http://www.whatwg.org/specs/web-workers/current-work


WebWorker란 쉽게 말해서 웹클라이언트에 멀티쓰레드를 할 수 있게 해주는 것이다.

[장점]
1. 메인 스크립트에서 긴 연산을 수행할 때 브라우저가 어는(freeze) 현상을 방지할 수 있다.
2. 빡센 계산을 할 때 멀티코어를 사용하여 병렬계산을 할 수 있다.

나머지야 응용하기 나름의 장점이 만들어 질테고 일단 가장 두드러지는 장점들은 저렇다.

그리고 "기존의 setInterval을 이용해도 병렬처리가 가능하지 않은가?" 이런 의문이 생길 수도 있는데, setInterval은 타이머 형식으로 메인쓰레드의 이벤트를 받아서 발생한다. 그러므로 만약 타이머 내부의 연산이 길어진다면 마찬가지로 브라우저가 얼어붙는다. 하지만 WebWorker는 멀티쓰레드이므로 그럴 일이 없다.

기존의 작업을 기준으로 본다면 그다지 쓸모가 없을 것 같아보이지만,
WebWorker에 의존하여 클라이언트에 많은 연산을 넘기게 된다면 활용도가 커질 수 있다고 본다.
물론 WebWorker 단독이 아니라 canvas, WebStorage, WebSocket 등의 다른 것들을 함께 이용해서.

만약 그렇게 된다면 서버의 자원을 더 많이 확보할 수 있게 되므로 이익이다..-_-;





WebWorker 이용에 있어서 제약사항은 다음과 같다.

1. 보안상의 이유로 WebWorker는 DOM 객체에 접근할 수 없다.
2. 뭐 동일한 이유로 alert() 등도 사용할 수 없다. (디버깅이 엿같을지도)
  - 하지만 다음 객체들은 사용가능
    - navigator
    - location
    - XMLHttpRequest(WebSocket이 들어오면 필요할까?)
    - 그외 Date, String 등 자바스크립트 기본 객체들
3. 통신에 사용되는 postMessage() 함수의 매개변수로 객체를 넘길 수 있지만 call by value 이므로 역시 DOM 객체에 접근불가
4. 각 브라우저에서 구현한 WebWorker가 프로세스의 형태이든 쓰레드의 형태이든 시동비용과 인스턴스당 메모리 사용량이 제법 되므로 왕창 생성해서 무지막지하게 돌리는 것은 권장되지 않음
5. 여러 페이지가 공유하는 형태의 WebWorker인 SharedWorker는 아직 구현된 브라우저가 없음 (2010.12.29)





WebWorker와 메인쓰레드의 자바스크립트는 postMessage와 onmessage 이벤트 핸들러를 통해서 통신할 수 있다.

아래는 드래프트 문서에도 있는 가장 간단한 WebWorker 예제인 소수(prime number) 구하기 예제이다.
new Worker()를 통해 새로운 WebWorker를 생성한 다음,
새로 만들어진 WebWorker의 onmessage 이벤트 핸들러를 지정하고,
worker.js 에 정의되어 있는 WebWorker는 무한루프를 돌면서 소수를 찾으면 postMessage()를 이용해 메인쓰레드에 메세지를 보내는 형태이다.

index.html
<!DOCTYPE HTML>
<html>

<head>
    <title>WebWorker를 이용하여 소수 구하기</title>
</head>

<body>

<output id="result"></output>

<script>
var worker = new Worker('worker.js');
worker.onmessage = function(event)
{
    document.getElementById('result').textContent = event.data;
}
</script>

</body>
</html>


worker.js
var n = 1;
search: while (true) {
    n += 1;
    for (var i=2; i<=Math.sqrt(n); i+=1)
        if (n%i==0) continue search;

    // 소수를 찾았음
    postMessage(n);
}






다음 예제는 멀티코어를 이용한 병렬계산 테스트이다.
내 듀얼코어 PC에서 확인한 결과 단일쓰레드는 4.X초, 멀티쓰레드는 2.X초가 걸리는 걸로 보아 되는 것 같다...-_-;
쓰레드 수를 늘려가며 테스트를 해봤을 때 쓰레드를 2개 이상 생성해도 별 차이가 없는 걸로 봐서는,
코어의 갯수만큼까지는 쓰레드의 수를 늘리면 효율이 좋아지지만 그 이상은 최대효율과 그다지 차이가 없다.

index.html
<!DOCTYPE HTML>
<html>

<head>

<script>
// 밀리세컨즈 반환
function getMS()
{
    return new Date.getTime();
}

// 최대값
var max = 2000000;
var start = 0;

// 단일 쓰레드
function one_thread()
{
    var n = 1;
    var count = 0;

    start = getMS();

    search: while (n<=max) {
        ++n;
        for (var i=2; i<=Math.sqrt(n); i++) {
            if (n%i==0) continue search;
        }

        ++count;
    }

    var elapsed = getMS() - start;

    document.getElementById('result').innerHTML = count+'<br />경과시간: '+elapsed;
}

// 멀티 쓰레드
var workers = [];
var inc = 500000;
var result = [];
function multi_thread()
{
    workers = [];
    result = [];
    start = getMS();
    var i = 1, j = 0;
    while (i<=max) {
        workers[j] = new Worker('worker.js');
        workers[j].onmessage = msg;
        workers[j].postMessage({'begin': i, 'end': i+inc-1});
        
        i += inc;
        ++j;
    }
}

// worker의 이벤트 핸들러
function msg(event)
{
    // 결과 모으기
    result.push(parseInt(event.data));

    // 결과가 다 모였다면 최종결과 표시
    if (result.length==workers.length) {
        var count = 0;
        for (var i=0; i<result.length; i++) count += result[i];

        var elapsed = getMS() - start;
        document.getElementById('result').innerHTML = count+'<br />경과시간: '+elapsed;
    }
}
</script>

</head>

<body>

<span id="result">0</span>
<br /><br /><br />
<input type="button" value="단일 쓰레드" onclick="one_thread();" />
&nbsp;
<input type="button" value="멀티 쓰레드" onclick="multi_thread();" />

</body>
</html>


worker.js
var begin = end = 0;

onmessage = function(event)
{
    begin = event.data.begin;
    end    = event.data.end;

    var n = begin;
    var countPrime = 0;
    search: while (n<=end) {
        n += 1;
        for (var i=2; i<=Math.sqrt(n); i++) 
            if (n%i==0) continue search;

        // 소수를 찾았음
        ++countPrime;
    }
   
    postMessage(countPrime);
};





Posted by bloodguy
,