>  기사  >  웹 프론트엔드  >  JavaScript 싱글스레딩에 대한 자세한 소개(그림)

JavaScript 싱글스레딩에 대한 자세한 소개(그림)

黄舟
黄舟원래의
2017-03-10 14:30:371171검색
먼저 JavaScript가 싱글 스레드인 이유는 무엇입니까? 우리 모두 알고 있듯이 JavaScript는 단일 스레드 방식으로 실행됩니다. 스레드에 관해 이야기할 때 우리는 자연스럽게 프로세스를 떠올립니다. 그렇다면 그들 사이의 연관성은 무엇입니까?

프로세스와 스레드는 운영체제의 개념입니다. 프로세스는 애플리케이션의 실행 인스턴스입니다. 각 프로세스는 개인 가상 주소 공간, 코드, 데이터 및 기타 시스템 리소스로 구성되며, 프로세스는 작업 중에 시스템 리소스(예: 독립 메모리 영역)를 생성하고 사용합니다. . , 프로세스가 종료되면 이러한 리소스도 삭제됩니다. 스레드는 프로세스 내의 독립적인 실행 단위이며 프로세스 리소스는 서로 다른 스레드 간에 공유될 수 있으므로 멀티스레딩의 경우 중요한 리소스에 대한 접근 제어에 특별한 주의가 필요합니다. 시스템은 프로세스를 생성한 후 해당 프로세스를 실행하는 메인 스레드를 시작합니다. 프로세스의 수명 주기는 메인 스레드의 수명 주기와 일치합니다. 메인 스레드의 종료는 프로세스의 종료 및 소멸을 의미합니다. 메인 스레드는 시스템 프로세스에 의해 생성되며, 사용자는 다른 스레드를 독립적으로 생성할 수도 있습니다. 이 일련의 스레드는 동일한 프로세스에서 동시에 실행됩니다.

분명히 멀티 스레드 작업에서 애플리케이션의 병렬 처리가 가능하므로 CPU 사용률이 높아져 전체 애플리케이션의 성능과 처리량이 향상됩니다. 특히 이제 많은 언어가 멀티 코어 병렬 처리 기술을 지원하므로 JavaScript가 단일 스레드에서 실행되는 이유는 무엇입니까?

사실 이는 목적과 관련이 있습니다. 브라우저 스크립팅 언어인 JavaScript의 주요 목적은 사용자와 상호 작용하고 DOM을 조작하는 것입니다. 이러한 DOM이 멀티스레드 방식으로 작동되는 경우 작업 충돌이 발생할 수 있습니다. DOM 요소를 동시에 작동하는 두 개의 스레드가 있다고 가정합니다. 스레드 1에서는 브라우저가 DOM을 삭제해야 하고 스레드 2에서는 DOM 스타일을 수정해야 합니다. 이때 브라우저는 어떤 스레드를 사용할지 결정할 수 없습니다. 물론 이러한 충돌을 해결하기 위해 브라우저에 "잠금" 메커니즘을 도입할 수 있지만 이로 인해 복잡성이 크게 증가하므로 JavaScript는 탄생 이후 단일 스레드 실행을 선택했습니다.

또한 JavaScript는 단일 스레드이므로 특정 시간에 특정 작업 하나만 실행할 수 있으며 다른 작업의 실행은 차단됩니다. 그러면 I/O와 같이 시간이 많이 걸리는 작업의 경우 후속 작업을 계속하기 전에 해당 작업이 완료될 때까지 기다릴 필요가 없습니다. 이러한 작업이 완료되기 전에 JavaScript는 계속해서 다른 작업을 수행할 수 있습니다. 이러한 시간이 많이 소요되는 작업이 완료되면 해당 처리가 콜백 형태로 수행됩니다. 비동기식 및 콜백은 JavaScript의 고유한 기능입니다.

물론, 시간이 많이 걸리는 불가피한 작업(예: 과도한 작업, 다중 루프)을 위해 HTML5는 Worker 클래스를 사용하여 현재 JavaScript 실행 메인 스레드에 추가 스레드를 생성하는 Web Worker를 제안합니다. 특정 JavaScript 파일을 로드하고 실행하면 이 새 스레드와 JavaScript 메인 스레드는 서로 영향을 주거나 실행을 차단하지 않으며 Web Worker는 이 새 스레드와 JavaScript 메인 스레드 간의 데이터 교환을 위한 인터페이스인 postMessage 및 onMessage 이벤트를 제공합니다. 하지만 HTML5 Web Worker에서는 DOM을 조작할 수 없습니다. DOM 운영이 필요한 모든 작업은 JavaScript 메인 스레드에 맡겨 실행해야 합니다. 따라서 HTML5 Web Worker가 도입되었지만 JavaScript의 단일 스레드 특성은 다음과 같습니다. 아직도 변하지 않았습니다.

동시성 모드 및 이벤트 루프

JavaScript에는 "이벤트 루프"를 기반으로 하는 동시성 모델이 있습니다.

아, 동시성? 자바스크립트는 싱글스레드라고 하지 않나요? 예, 실제로는 단일 스레드이지만 동시성과 병렬성에는 차이가 있습니다. 전자가 논리적 동시성이라면, 후자는 물리적 동시성이다. 따라서 단일 코어 프로세서도 동시성을 달성할 수 있습니다.

동시성과 병렬성

병렬은 누구나 이해하기 쉬운 말로, 소위 '동시성'이란 둘 이상의 사건이 동시에 일어나는 것을 의미합니다. 위의 첫 번째 표에서 보듯이, 컴퓨터 시스템에는 CPU가 1개밖에 없기 때문에 "마이크로" 관점에서 세 개의 프로그램 ABC가 CPU를 교대로 사용하지만 교대 시간이 매우 짧고 사용자가 이를 알아차릴 수 없어 동시성을 형성합니다. "매크로" 감각이 작동합니다.

JavaScript 싱글스레딩에 대한 자세한 소개(그림)런타임 개념

다음 내용은 이론적 모델을 설명합니다. 최신 JavaScript 엔진은 아래 설명된 몇 가지 개념을 구현하고 최적화하는 데 중점을 두었습니다.

Stack(스택)

다음은 JavaScript가 실행하는 작업입니다. 각 작업을 프레임 스택이라고 합니다.

function f(b){
  var a = 12;
  return a+b+35;
}

function g(x){
  var m = 4;
  return f(m*x);
}

g(21);

위 코드가 g을 호출하면 g의 매개변수와 지역 변수가 포함된 스택의 첫 번째 프레임이 생성됩니다. gf을 호출하면 두 번째 프레임이 생성되어 첫 번째 프레임 위에 배치됩니다. 물론 이 프레임에는 f의 매개변수와 지역 변수도 포함됩니다. f이 반환되면 해당 프레임이 스택에서 사라집니다. 마찬가지로 g가 반환되면 스택은 비어 있습니다(스택의 구체적인 정의는 LIFO(후입선출)입니다).

객체가 할당되는 메모리의 구조화되지 않은 대규모 영역을 나타내는 데 사용되는 이름입니다.

큐(queue)

JavaScript 런타임에는 처리할 일련의 작업으로 구성된 작업 큐가 포함되어 있습니다. 각 작업에는 해당 기능이 있습니다. 스택이 비어 있으면 작업 대기열에서 작업을 가져와 처리합니다. 이 프로세스는 작업과 관련된 일련의 함수를 호출합니다(따라서 초기 스택 프레임 생성). 작업이 처리되면 스택은 다시 비게 됩니다. (큐의 특징은 선입선출(FIFO)입니다.)

설명과 이해를 돕기 위해 다음과 같은 규칙을 따릅니다.

  • 스택 스택이 메인 스레드입니다

  • 대기열 대기열은 작업 대기열입니다(실행을 위해 메인 스레드에 대한 예약을 기다리는 중)

자, 위의 지식 포인트는 JavaScript 런타임과 관련된 개념을 명확히 하는 데 도움이 됩니다. 후속 분석.

이벤트 루프

유사한 방식으로 구현되었기 때문에 이벤트 루프라고 합니다.

while(queue.waitForMessage()){
  queue.processNextMessage();
}

위에서 언급했듯이 "작업 큐"는 이벤트 큐입니다. I/O 장치가 작업을 완료하거나 사용자가 이벤트(이벤트가 콜백 함수를 지정함)를 트리거하면 관련 이벤트 처리 기능이 "작업 대기열"에 들어가고, 메인 스레드가 유휴 상태일 때 "작업의 세 번째 스레드가 됩니다. 대기열"이 예약됩니다. 보류 중인 작업(FIFO)입니다. 물론 타이머의 경우 지정된 시간에 도달하면 해당 작업이 "작업 대기열"의 끝에 삽입됩니다.

"완료까지 실행"

작업이 실행될 때마다 다른 작업이 실행됩니다. 즉, 함수가 실행되면 대체될 수 없으며 다른 코드가 실행되기 전에 완료됩니다.
물론 이는 이벤트 루프의 단점이기도 합니다. 작업을 완료하는 데 너무 오랜 시간이 걸리면 애플리케이션이 사용자 상호 작용(예: 클릭 이벤트)을 제때에 처리할 수 없으며 심지어 애플리케이션이 충돌할 수도 있습니다. 더 나은 해결책은 작업 완료 시간을 단축하거나 작업을 여러 작업으로 나누어 실행하는 것입니다.

차단하지 않음

JavaScript는 다른 언어와 다릅니다. 이벤트 루프의 특징 중 하나는 절대 차단하지 않는다는 것입니다. I/O 작업은 일반적으로 이벤트 및 콜백 함수를 통해 처리됩니다. 따라서 애플리케이션이 indexedDB 또는 XHR 비동기 요청이 반환되기를 기다리는 동안에도 다른 작업(예: 사용자 입력)을 처리할 수 있습니다.

경고나 동기 XHR과 같은 예외가 있지만 이를 피하는 것이 모범 사례로 간주됩니다. 예외에 대한 예외가 존재한다는 점에 유의하십시오(그러나 일반적으로 다른 이유보다는 구현 오류로 인해 발생함).

타이머

타이머의 일부 개념

위에서 언급한 것처럼 지정된 시간에 도달하면 타이머는 "작업 대기열"의 끝에 해당 콜백 함수를 삽입합니다. " . 이것이 "타이머" 기능이다.

타이머에는 setTimeout과 setInterval이라는 두 가지 메소드가 포함되어 있습니다. 두 번째 매개변수는 콜백 함수가 지연되어야 하는 시간(밀리초)을 지정합니다.
두 번째 매개변수에 관해 주의할 사항은 다음과 같습니다.

  • 두 번째 매개변수가 기본값인 경우 기본값은 0입니다.

  • 지정된 값이 4밀리초 미만인 경우 4ms로 늘립니다(HTML5 표준에서는 4ms, 2010년 이전 브라우저에서는 10ms로 지정됨).

이해하시면 위의 지식을 바탕으로 다음 코드는 문제가 되지 않습니다.

console.log(1);
setTimeout(function(){
    console.log(2);
},10);
console.log(3);
// 输出:1 3 2

타이머에 대한 심층적인 이해

Zero Delay setTimeout(func, 0)

지연이 0이라고 해서 콜백 함수가 즉시 실행되는 것은 아닙니다. 이는 메인 스레드가 현재 유휴 상태인지 여부와 "작업 대기열"에서 그 앞에 대기 중인 작업에 따라 달라집니다.

다음 코드를 보세요:

(function () {

  console.log('this is the start');

  setTimeout(function cb() {
    console.log('this is a msg from call back');
  });

  console.log('this is just a message');

  setTimeout(function cb1() {
    console.log('this is a msg from call back1');
  }, 0);

  console.log('this is the  end');

})();

// 输出如下:
this is the start
this is just a message
this is the end
undefined // 立即调用函数的返回值
this is a msg from callback
this is a msg from a callback1

setTimeout(func, 0)의 역할

  • 브라우저가 렌더링하도록 합니다. 현재 변경 사항(많은 브라우저 UI 렌더링 및 js 실행이 스레드에 배치되고 스레드 차단으로 인해 인터페이스 업데이트 및 렌더링이 실패함)

  • "스크립트가 실행 중인지 재평가합니다. 너무 길다" 경고

  • 실행 순서 변경

다음 코드를 다시 살펴보세요.

<button id=&#39;do&#39;> Do long calc!</button>
<p id=&#39;status&#39;></p>
<p id=&#39;result&#39;></p>

$(&#39;#do&#39;).on(&#39;click&#39;, function(){

  $(&#39;#status&#39;).text(&#39;calculating....&#39;);// 此处会触发redraw事件,但会放到队列里执行,直到long()执行完。

  // 没设定定时器,用户将无法看到“calculating...”
  long();// 执行长时间任务,造成阻塞

  // 设定了定时器,用户就如期看到“calculating...”
  //setTimeout(long,50);// 大约50ms后,将耗时长的long回调函数插入“任务队列”末尾,根据先进先出原则,其将在redraw之后被调度到主线程执行

 });

function long(){
  var result = 0
  for (var i = 0; i<1000; i++){
    for (var j = 0; j<1000; j++){
      for (var k = 0; k<1000; k++){
        result = result + i+j+k
      }
    } 
  }
  $(&#39;#status&#39;).text(&#39;calclation done&#39;); // 在本案例中,该语句必须放到这里,这将使它与回调函数的行为类似
}

원본과 복제본의 차이점 setInterval

setTimeout이 setInterval의 효과를 모방할 수 있다는 것은 누구나 알 수 있습니다. 다음 코드의 차이점을 살펴보겠습니다.

// 利用setTimeout模仿setInterval
setTimeout(function(){
    /* 执行一些操作. */
    setTimeout(arguments.callee, 10);
}, 1000);

setInterval(function(){
    /* 执行一些操作 */
}, 1000);

아마도 여러분은 이렇게 생각할 것입니다. 차이가 없습니다. 실제로 콜백 함수에서의 연산이 짧은 시간이 걸릴 경우에는 둘 사이의 차이를 볼 수 없습니다.

其实:上面案例中的 setTimeout 总是会在其回调函数执行后延迟 10ms(或者更多,但不可能少)再次执行回调函数,从而实现setInterval的效果,而 setInterval 总是 10ms 执行一次,而不管它的回调函数执行多久。

所以,如果 setInterval 的回调函数执行时间比你指定的间隔时间相等或者更长,那么其回调函数会连在一起执行。

你可以试试运行以下代码:

var counter = 0;
var initTime = new Date().getTime();
var timer = setInterval(function(){
    if(counter===2){
        clearInterval(timer);
    }
    if(counter === 0){
        for(var i = 0; i < 1990000000; i++){
            ;
        }
    }

    console.log("第"+counter+"次:" + (new Date().getTime() - initTime) + " ms");

    counter++;
},1000);

我电脑Chrome浏览器的输入如下:

第0次:2007 ms
第1次:2013 ms
第2次:3008 ms

浏览器

浏览器不是单线程的

上面说了这么多关于JavaScript是单线程的,下面说说其宿主环境——浏览器。

浏览器的内核是多线程的,它们在内核制控下相互配合以保持同步,一个浏览器至少实现三个常驻线程:

  1. javascript引擎线程 javascript引擎是基于事件驱动单线程执行的,JS引擎一直等待着任务队列中任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS线程在运行JS程序。

  2. GUI渲染线程 GUI渲染线程负责渲染浏览器界面,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。但需要注意GUI渲染线程与JS引擎是互斥的,当JS引擎执行时GUI线程会被挂起,GUI更新会被保存在一个队列中等到JS引擎空闲时立即被执行。

  3. 浏览器事件触发线程 事件触发线程,当一个事件被触发时该线程会把事件添加到“任务队列”的队尾,等待JS引擎的处理。这些事件可来自JavaScript引擎当前执行的代码块如setTimeOut、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS是单线程执行的,所有这些事件都得排队等待JS引擎处理。

在Chrome浏览器中,为了防止因一个标签页奔溃而影响整个浏览器,其每个标签页都是一个进程。当然,对于同一域名下的标签页是能够相互通讯的,具体可看 浏览器跨标签通讯。在Chrome设计中存在很多的进程,并利用进程间通讯来完成它们之间的同步,因此这也是Chrome快速的法宝之一。对于Ajax的请求也需要特殊线程来执行,当需要发送一个Ajax请求时,浏览器会开辟一个新的线程来执行HTTP的请求,它并不会阻塞JavaScript线程的执行,当HTTP请求状态变更时,相应事件会被作为回调放入到“任务队列”中等待被执行。

看看以下代码:

document.onclick = function(){
    console.log("click")
}

for(var i = 0; i< 100000000; i++);

解释一下代码:首先向document注册了一个click事件,然后就执行了一段耗时的for循环,在这段for循环结束前,你可以尝试点击页面。当耗时操作结束后,console控制台就会输出之前点击事件的”click”语句。这视乎证明了点击事件(也包括其它各种事件)是由额外单独的线程触发的,事件触发后就会将回调函数放进了“任务队列”的末尾,等待着JavaScript主线程的执行。

总结

  • JavaScript是单线程的,同一时刻只能执行特定的任务。而浏览器是多线程的。

  • 异步任务(各种浏览器事件、定时器等)都是先添加到“任务队列”(定时器则到达其指定参数时)。当Stack栈(JS主线程)为空时,就会读取Queue队列(任务队列)的第一个任务(队首),然后执行。

JavaScript为了避免复杂性,而实现单线程执行。而今JavaScript却变得越来越不简单了,当然这也是JavaScript迷人的地方。


위 내용은 JavaScript 싱글스레딩에 대한 자세한 소개(그림)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.