본문으로 건너뛰기

Web Workers 이해하기

무료2015-12-02#JS#web worker#JavaScript Web Worker#js Worker#js SharedWorker#Web Workers API

Web Workers 란 무엇인가, 무엇에 유용한가, 어떻게 사용하는가

일.Web Workers 란 무엇인가

Web Workers 는 태스크를 백그라운드 스레드에서 실행하는 것을 지원하는 메커니즘입니다. 맞습니다, Web Worker 는동작이 제한된 멀티스레드를 제공합니다

브라우저 내 js 의 실행 환경은 여전히 싱글스레드이지만, 멀티스레드는 브라우저에는 예전부터 존재했습니다. 예를 들어 비동기 XHR 은 동작이 제한된 스레드이며, 메인 스레드에서 독립되어 있고, 내장된 메시지 메커니즘을 통해 통신하며, 일반적인 멀티스레드의 병행 문제는 존재하지 않습니다

Web Workers 는 메인 스레드에서 독립된 context 를 제공하며, 이 context 내에서는 제한된 API 를 사용할 수 있습니다 (DOM 조작은 금지, Cache 에 접근하지 못할 수 있음 등). 멀티스레드의 가장 큰 장점은 UI 를 블록하지 않는다는 것으로, 멀티스레드를 사용하여 시간이 많이 걸리는 태스크 (예: 오디오/비디오 디코딩, 및 이를 기반으로 한 데이터 처리) 를 완료할 수 있습니다. API 가 허용한다면, Web Workers 를 사용하여 XHR 을 직접 구현할 수도 있습니다

P.S.Web Worker 가 Cache 에 접근하는 것을 허용할かどうか는 현재 (2015/11/29) 아직 논쟁 중입니다. Chrome43 과 FF 는 지원하며, IE 와 Safari 의 지원 상황은 알 수 없습니다

약간의 잡담:Google 의 Gears 플러그인은 Worker Pool API 를 제안했습니다. 그것이 Web Workers 의 "원형"입니다. 처음에는 브라우저 기능을 강화하기를 희망했습니다. 예를 들어 오프라인 브라우징 지원 (오프라인에서 캐시 페이지에 접근하고, 재온라인 후 오프라인 작업을 제출) 등이지만, 현재 (2015/11/29) 는 이미 폐기되었습니다

Google Gears API

The Google Gears API is no longer available. Thank you for your interest.

이.Web Workers 는 무엇에 유용한가

백그라운드 스레드의 가장 큰 역할은 do stuff 입니다. 백그라운드 스레드를 지원하는 것은 브라우저가 더 많고 더 무거운 태스크를 감당할 수 있음을 의미합니다. 서버 측이 담당하던 일부 연산 (예: 정렬, 인코딩/디코딩, 템플릿 생성) 을 클라이언트 측으로 이관하여 서버 측의 압력을 경감할 수 있습니다. 사용자 경험에 영향을 주지 않으면서 (서버 측의 결과를 기다리는 것도, 로컬의 다른 스레드의 결과를 기다리는 것도 모두 대기이며, 후자가 더 빠를 수 있습니다)

필자는 Web Workers 에는 다음과 같은 적용 시나리오가 있다고 생각합니다:

  • 오디오/비디오 디코딩

audioContext.decodeAudioData 등의 조작을 시도해 보면, "무거운 일"을 할 수 있는 백그라운드 스레드를 절실히 필요로 한다는 것을 알게 될 것입니다

  • 이미지 전처리

예를 들어 아바타 업로드 전의 자르기, 심지어 워터마크 추가, 합성, 모자이크 추가 등. 클라이언트 측에서 완료할 수 있다면 대량의 임시 파일 전송을 피할 수 있습니다

  • 정렬 등의 데이터 처리 알고리즘

서버 압력 경감

  • 클라이언트 템플릿

예를 들어 markdown, 또는 서버 측이 JSON 을 반환하고, 클라이언트 측이 취득한 후 백그라운드 스레드에 넘겨 해석하고, 템플릿 HTML 을 적용하여 페이지를 생성. 이러한 조작을 모두 클라이언트 측에서 완료하면, 전송해야 할 것이 더 적어집니다

삼.Web Workers 사용 방법

말할 수 있는 샘플 코드는 다음과 같습니다:

//--- 主 페이지
if (window.Worker) {
    var worker = new Worker('worker.js');
    var data = {a: 1, b: [1, 2, 3], c: 'string'};
    worker.postMessage(data);
    worker.onmessage = function(e) {
        console.log('main thread received data');
        console.log(e.data);

        // 接到消息立即停止 worker,onerror 将不会触发
        // worker.terminate();
        // terminate 之后收不到后续消息,但 post 不报错
        // worker.postMessage(1);
    }
    worker.onerror = function(err) {
        console.log('main thread received err');
        console.log(err.message);
        // 阻止报错
        err.preventDefault();
    }
}
//---end 主 페이지

//---worker.js
// 可以引入其它依赖
// importScripts('lib.js');
// importScripts('a.js', 'b.js');
onmessage = function(e) {console.log(self); // 看看 global 变量身上有些什么
    var data = e.data;
    console.log('worker received data');
    console.log(data);

    var res = data;
    res.resolved = true;

    postMessage(res);

    setTimeout(function() {
        throw new Error('error occurs');

        // close,立即停止,相当于主线程中的 worker.terminate()
        // close();
    }, 100);
};
//---end worker.js

주의:

  1. worker 의 global context 는 window아니라, self 입니다. self 도 일련의 인터페이스를 제공하며, self.JSON, self.Math, self.console 등을 포함합니다. 가장 직관적인 차이는 document 객체가 없어지지만, location, navigator 는 남아 있습니다

  2. Worker 의 원형은 DedicatedWorkerGlobalScope 로, SharedWGS 와는 다릅니다. Worker 는 전용이며, Worker 를 생성한 스크립트만이 자신이 생성한 worker 에 접근할 수 있습니다

  3. worker 내에서는 DOM 조작이 허용되지 않지만, WebSocket, IndexedDB, XHR 등을 지원합니다. 각 브라우저의 지원 상황에는 차이가 있습니다. 자세한 내용은 Functions and classes available to Web Workers - Web APIs | MDN 를 참조하십시오

주의:XHR 은 사용할 수 있지만, XHR 객체의 responseXMLchannel 속성은 항상 null 을 반환합니다

  1. 메인 스레드와 worker 스레드는 메시지 송수신 방식이 일치합니다 (postMessage 로 전송, onmessage/onerror 로 수신, 데이터는 MessageEvent 의 data 속성에서 취득). 스레드 간에 전달되는 것은 값의 copy 이지, 공유 참조가 아닙니다

  2. IE10 과 FF 는 worker 내에서의 Worker new 를 지원합니다. 이러한 worker 가 부모 페이지와 동원이어야 합니다

  3. 지원성 문제에 대해서는 기능 감지를行うことを 권장합니다

    if (window.Worker) {
        // new Worker...
    }
    
  4. importScripts 는 다른 js 파일을 도입할 수 있습니다. 외부 파일 내의 글로벌 변수는 self 에 붙여지며, worker 내에서 직접 참조할 수 있습니다. importScripts 는 동기적이며, 다운로드와 실행 완료 후 다음 행을 실행합니다

사.Shared Worker

위에서 주목한 Worker 는 dedicated worker(전용 worker) 에 속하며, worker 객체를 생성한 context 만이 worker 에 대한 접근권을 가집니다. Shared Worker 는 cross context 의 worker 공유를 지원합니다. 예를 들어 window 와 iframe, iframe 과 iframe

그것들은 공유 속성을 가지므로, 다른 윈도우 내의 애플리케이션의 같은 상태를 유지할 수 있으며, 다른 윈도우의 페이지는 동일한 공유 worker 스크립트를 통해 상태를 유지 및 보고합니다

localStorage 를 사용해도 상태 동기화를 실현할 수 있습니다. zxx 선배의 예: Page Visibility( 网页可见性) API 와 로그인 동기화 유도 페이지 实例 페이지. 흥미가 있으면 원문을 참조하십시오: Page Visibility( 페이지 가시성) API 소개, 미확장

필자가 생각할 수 있는 Shared Worker 의 유일한 적용 시나리오는 애플리케이션 상태 동기화 메커니즘의 구현일 것입니다. cookie, localStorage 도 상태를 저장할 수 있지만, Shared Worker 는 일련의 복잡한 상태 제어 메커니즘을 실현할 수 있습니다. 간단히 말해, cookie 와 localStorage 에는 데이터만 넣을 수 있지만, Shared Worker 에는 데이터를 넣을 수 있을 뿐만 아니라 코드도 실행할 수 있습니다

게다가, Shared Worker 는 별로 유용하지 않은 것 같습니다. 결국 new Worker 할 수 있는已经很不错了.锦上添花의 것의 역할은 사람을 놀라게 하지 않을 것입니다

샘플 코드는 다음과 같습니다:

//--- 主 페이지
if (window.SharedWorker) {
    var sworker = new SharedWorker('sharedworker.js');

    // 1.onmessage 隐式调用 start
    // sworker.port.onmessage = function(e) {
    //     console.log('main thread received data:');
    //     console.log(e.data);
    // }
    // 2.或者 addEventListener 再显式调用 start
    sworker.port.addEventListener('message', function(e) {
        console.log('main thread received data:');
        console.log(e.data);
    });
    sworker.port.start();

    sworker.port.postMessage([1, 2, 3]);
}
//---end 主 페이지

//---sharedworker.js
var count = 0;

onconnect = function(e) {
    // 记录连接数
    count++;

    var port = e.ports[0];

    // 1.onmessage 隐式调用 start
    port.onmessage = function(e) {
        //!!! console.log 失效了
        console.log('will not occur in console');
        
        port.postMessage({receivedData: e.data, count: count});

        //!!! 错误不会抛回给 sharedworker 创建者,静默失败
        //!!! 如果 sharedworker 本身有语法错误,也会静默失败,而且在这之前的 postMessage 也将无效
        // a*; // 制造一个语法错误
        
        setTimeout(function() {
            throw new Error('error occurs');
        }, 100);
    };
    // 2.或者 addEventListener 再显式调用 start
    // port.addEventListener('message', function(e) {
    //     //!!! console.log 失效了
    //     console.log('will not occur in console');

    //     port.postMessage('sharedworker received data: ' + e.data);
    // });
    // port.start();
};
//---end sharedworker.js

온라인 DEMO: testSharedWorker DEMO

테스트 방법:위의 링크를 열고, console 을見て count 가 1 인 것을 확인. 어떻게 리프레시해도 1 그대로. 다른 탭에서 해당 페이지를 열면, count 가 2 가 됨. 그 후 리프레시를 반복하면, count 가 5 가 됨. 이전의 탭으로 돌아가, 리프레시하면, count 가 6 이 됨...

오.스레드 안전 문제

스레드 안전의 면에서는, 병행에 의해 야기되는各种问题는존재하지 않습니다. Worker 스레드의 동작은 제한되어 있기 때문입니다 (worker 는 비스레드 안전한 컴포넌트와 DOM 에 접근할 수 없습니다)

육.기타 Worker

위에서 논의한 Web Workers 외에, 이러한 Worker 가 있습니다:

  • ServiceWorkers( 프록시 서버에 유사하며, 오프라인 서비스를 제공)

  • ChromeWorker( 브라우저 플러그인 개발에서 사용 가능한 Worker)

  • Audio Workers( 오디오 처리를 지원, 브라우저 지원 없음, 2015/11/29 현재)

참고 자료

댓글

아직 댓글이 없습니다

댓글 작성