이글은 medium 포스트 글을 번역하였습니다.
Javascript는 JAVA, C+, Python등의 멀티쓰레드를 지원하는 언어와 다르게 싱글쓰레드 (single-thread)로써, 멀티쓰레드의 장점을 사용할 수 없다는 것을 알고 있을 것입니다. 이는 여러 프로세스를 실행할 수 있는 옵션이 제공되지 않고, 모든 프로세스를 실행할 단일 쓰레드를 사용합니다.
먼저, 싱글쓰레드가 의미하는 바를 먼저 살펴보겠습니다. 용어 자체에서 알 수 있듯이 싱글쓰레드 (단일 쓰레드) 어플리케이션은 특정 시점의 하나의 프로세스만 실행할 수 있습니다. 이러한 프로세스는 콜스택 (Call Stack)
이라는 데이터 구조에 배치됩니다. 콜스택은 최상위 프로세스가 완료되면 스택 내에서 pop(제거)
되고, 스택 내에 존재하는 다음 프로세스 혹은 메소드가 구현됩니다. 이는 스택 내에 프로세스가 모두 없어질때까지 발생합니다.
콜스택을 더 잘 이해할 수 있는 간단한 예제를 살펴 보겠습니다.
function one(){
return two();
}
function two(){
return three();
}
function three(){
return four();
}
function four(){
return "completed";
}
console.log(one());
위의 코드에서, 메소드 one
을 호출하면, 호출 스택에 추가됩니다. 그러나 one
이라는 메소드는 전체가 실행될때 까지 콜스택 내에 존재할 것입니다.
메소드가 호출되는 진행 방향을 살펴보겠습니다.
one -> two -> three ->four
*calls = ->
콜스택은 다음과 같은 방향으로 진행될 것입니다.
+-----------+ +-----------+ +-----------+ +-----------+
|Call Stack | ->|Call Stack | ->|Call Stack | ->|Call Stack |
+-----------+ +-----------+ +-----------+ +-----------+
| four | | three | | two | | one |
| three | | two | | one | +-----------+
| two | | one | +-----------+
| one | +-----------+
+-----------+
네개의 function이 실행을 끝내면 콜스택에서 제거됩니다. 스택에서는 더이상 스택에 남아있는 작업이 없을 때까지 스택의 맨 위의 다음 태스크 (3개 - one, two, three)로 자동 진행 됩니다.
콜스택이 어떻게 작동하는지에 대한 기본적인 개념을 익히면 좋을것 같습니다. 이후부터는 좀더 까다로운 문제를 다뤄보겠습니다.
Javascript가 싱글쓰레드를 채택하고 있더라도, 싱글쓰레드 자체로 동작하고 있지는 않습니다. 이 경우는 서비스가 호출 될 때마다 콜스택이 다른 모든 프로세스를 차단하고 서비스가 완료될때까지 대기한 다음 다음 작업으로 진행됩니다. 이렇게하면 실행 시간이 길어질 가능성은 있지만, Javascript내에서 사용하는 경우는 좀 다르게 실행됩니다.
이전에 작성한 예제를 수정하여 서비스 호출로 동작하게 되는 setTimeout
함수를 추가해 보겠습니다.
function service(){
console.log("service called")
}
function one(){
return two();
}
function two(){
return three();
}
function three(){
return four();
}
function four(){
return "completed";
}
setTimeout(service,1000)
console.log(one());
이제 setTimeout
은 콜백함수를 매개변수로 사용하는 비동기 메서드가 됩니다. 두번째 매개변수의 경우 해당 콜백이 트리거될 시간을 표시합니다.
비동기 함수가 실행되는 과정을 이해해 보겠습니다. 함수를 실행하려면 함수를 호출해야 됩니다. 트리거를 사용하여 호출이 될 수 있으며, 이 트리거는 일반적으로 onClick
, onBlur
, timeout
과 같은 이벤트를 지칭합니다.
이벤트를 특정 메소드에 매핑하는 데이터 구조가 필요한데, 이러한 데이터 구조를 이벤트 테이블이라고 명칭합니다. 그러나, 이 이벤트테이블은 메소드를 이벤트로만 매핑하고 메소드 자체는 호출하지 않습니다. 대신에 이벤트테이블은 메소드를 이벤트 대기열로 보내고, 올바른 순서로 실행될 메소드 대기열을 지칭합니다.
Event loop
이벤트루프는 기본적으로 무한 루프이며, 콜스택에서 실행될 내용이 있는지 여부를 계속 확인합니다. 콜스택이 비어있으면, 콜스택으로 이동할 항목이 있는지 이벤트 큐를 검사합니다. 메서드가 존재하는 경우 해당 메서드를 콜스택으로 이동시키고 메서드가 실행됩니다.
아래 예를 살펴보겠습니다.
참고: 각 메서드 one, two, ,three, four가 실행을 완료하는데 500밀리세컨드가 걸린다고 가정합니다.
setTimeout이 실행되면 ->
setTimeout은 이벤트 테이블로 이동됩니다.
* one 메서드가 실행되면 ->
* one은 콜스택에 저장됩니다.
* one 메서드가 two 메서드를 실행하면 ->
* two 메서드는 콜스택에 저장됩니다.
* two 메서드가 three메서드를 실행하면 ->
* three 메서드가 콜스택에 저장됩니다.
* three 메서드가 four 메서드를 실행하면 ->
* four 메서드가 콜스택에 저장됩니다.
메서드 three가 완료될때까지는 100ms 이상이 소요됩니다.
이때 이벤트테이블은 서비스 메서드를 이벤트 대기열 (큐)로 보내게 됩니다.
모든 메서드가 실행되면, 콜스택이 비어있게 됩니다.
이벤트 루프는 이벤트 큐를 점검하고, 존재하는 서비스 메서드를 찾습니다.
서비스 메서드가 존재하는 경우, 이를 콜스택으로 옮기고 서비스가 실행됩니다.
요약하자면, 이벤트루프는 끝없이 실행되는 프로세스이고, 이벤트 큐에서 콜스택으로 메소드를 모니터링하고 전송하는 프로세스입니다. 콜스택 및 이벤트루프의 작동 방식을 이해하면, javascript가 페이지 내에서 어떻게 작동하는지 이해하는데 큰 도움이 됩니다.