JavaScript에서 매우 중요한 비동기 처리 방식을 공부하다 보면
JavaScript는 싱글 쓰레드 언어라는 말을 한 번쯤은 접했을 것이다.
실제로, JavaScript의 비동기 처리는 싱글 쓰레드와 아주 밀접한 관련이 있다.
이번 글에서는
1. 싱글 쓰레드 vs 멀티 쓰레드
2. 비동기란 무엇인지
두 주제에 대해 정리해보자.
1️⃣ 싱글 쓰레드 vs 멀티 쓰레드
사실, 쓰레드라는 용어를 정확히 이해하려면 프로세스를 먼저 알아야 된다.
하지만, 운영체제 영역이라 이 글과 벗어난 주제이기에 엄밀히 다루지 않겠다.
단순히, 쓰레드는 그저 프로그램 내 실행의 흐름이라고 생각하면 된다.
프로그램 내 실행의 흐름이라는 말이 어찌보면 애매모호하고 추상적일 수 있다.
예시를 들어보자.
for (let i = 0; i < 5; i++) {
console.log(1);
}
for (let i = 0; i < 5; i++) {
console.log(2);
}
이 프로그램을 실행시키면 당연히 1111122222 라는 결과가 순서대로 출력된다.
이러한 순서가 실행의 흐름이다. 저 코드 자체가 하나의 쓰레드라고 볼 수 있다.
그런데, 만약 저 두 for문을 동시에 실행시킬 수 있다면 어떻게 될까 ?
조금 더 구체적으로 들어가보면 , 지금은 1을 출력하는 첫 번째 for문이 실행된 후,
2를 출력하는 for문이 작동된다.
즉, 코드의 순서가 정해져 있다.
만약 이러한 순서를 무시하고, 저 두 for문이 동시에 실행된다면 출력 결과는 어떻게 될까 ?
아마도, 1211212221 이런 식으로 1,2 가 섞여서 출력될 것이다.
꼭 위의 출력이 아닐 수도 있다.
어떤 for문이 더 빠르냐에 따라 1112211222 이렇게 출력될 수도 있다.
본질은, 동시에 실행되었기 때문에 기존의 순서가 무너진다는 것에 있다.
예시를 구체적으로 들어보자.
function thread1() {
for (let i = 0; i < 5; i++) {
console.log(1);
}
}
function thread2(){
for (let i=0 ; i<5 ; i++){
console.log(2)
}
}
thread1()
thread2()
실행 결과는 당연히 1111122222 이다. thread1 -> thread2 순서로 실행되었기 때문이다.
이것이 싱글 쓰레드다.
만약 thread1 , thread2 가 동일 선상에서 동시에 작동한다면 동시에 for문이 돌아갈 것이다.
그렇다면 출력 결과는 1212122211 이런 식으로 1과 2가 섞인 채로 출력이 될 것이다.
이것이 멀티 쓰레드다.
JavaScript 만 공부하신 분들은 여기까지만 읽고 다음과 같은 의문점이 생길 수 있다.
그럼 도대체 thread1, thread2 를 어떻게 동시에 실행을 시키는데 ?
정답은 없다. 왜냐하면 JavaScript는 싱글 쓰레드 언어이기 때문이다.
싱글 쓰레드이므로 동시에 실행시킬 수가 없다.
물론, 방법이 아예 없는 것은 아니다.
Web Workers 라이브러리를 이용하면 멀티 쓰레드 환경을 구축할 수 있지만
이 마저도 완전한 멀티 쓰레드 환경이라고 볼 수는 없다.
Java는 멀티 쓰레드 언어이므로 위 두 함수를 동시에 실행시킬 수 있다.
물론 저렇게 단순하지는 않고 Thread 객체를 이용하여 구현해야 하므로 조금 복잡하다.
하지만 중요한 점은 언어 자체가 멀티 쓰레딩을 지원하므로 동시에 실행이 가능하다는 점이다.
그래서, 결론은 JavaScript 언어는 싱글 쓰레드이므로 코드가 동시에 실행되지 않고
순서대로 실행된다는 것이다.
2️⃣ JavaScript 의 비동기
먼저, 비동기가 무엇일까 ? 단순한 예시를 들어보겠다.
서버에서 데이터를 받아오려 한다.
서버에서 데이터를 받아오는 것은 네트워크를 통해 행해지기에 특정한 시간이 소모된다.
또 다른 예시로 setTimeout Web API 가 있다.
setTimeout은 특정 시간 후에 코드를 실행시키는 함수이다.
위 두 작업은 특정 시간이 걸린다는 공통점이 있다.
코드로 예시를 확인하자.
setTimeout(() => {
console.log(1)
}, 1000);
코드를 보면 1초 후, 콘솔 창에 1이 출력된다.
그런데, 이번에는 다음 예시를 보자.
const important = function() {
console.log('중요한 작업')
}
setTimeout(() => {
console.log('중요하지 않은 작업')
}, 1000);
important();
앞서 JavaScript 는 싱글 쓰레드 언어라고 했다.
그렇다면 실행 순서가 존재하고, 그 순서는 위에서 아래로 전해진다.
따라서, 위의 내용에 따른다면 setTimeout -> important() 순으로 실행될 것이다.
즉, 실행을 하면 1초 후 '중요하지 않은 작업'이 콘솔에 출력되고 이어서 '중요한 작업'이 출력될 것이다.
하지만 결과는
important() 가 먼저 실행되었다.
분명히 JavaScript는 싱글 쓰레드 언어이고, 위에서부터 아래로 순서에 맞게 실행이 되어야 하는데
순서에 맞지 않게 실행되었다.
이 부분이 비동기의 핵심이다.
실행 순서에 맞게 실행되는 것을 동기(Syncronous) 실행이라고 한다.
그렇다면 비동기 실행은 무슨 의미일까 ?
위의 결과처럼 특정 시간이 소모되는 작업이 완료될 때까지 기다리지 않고 이어서
계속 진행하는 것을 비동기(Asyncronous) 실행이라고 한다.
중요하지 않은 작업임에도 1초를 기다리고 중요한 작업을 실행하는 것은 매우 비효율적이다.
그렇다면 JS는 분명히 순서에 맞게 실행되는 싱글 쓰레드언어인데 어떻게 이것이 가능할까 ?
JavaScript 엔진은 독단적으로 실행되는 것이 아닌, 엔진을 실행시켜 주는 도구에 의해 실행된다.
이 도구가 바로 브라우저와 Node.js 인 것이며 이를 JavaScript 실행 환경, 즉 런타임이라고 부른다.
위의 런타임들은 특정 시간이 소모되는 작업을 따로 처리하고,
처리 후 실행해야될 함수를 Callback Queue 라는 어떠한 공간으로 보낸다.
이와 동시에 시간이 필요 없는 작업은 동기적으로 쭈욱 실행을 하게 된다.
동기적 작업이 모두 끝나면 Callback Queue 에 들어간 함수들을 Event Loop 라는 것을 통해
다시 JavaScript 엔진으로 돌아와 실행이 된다.
위의 용어들이 어렵고 낯설게 느껴질 수 있다. 또한, 100% 맞는 비유는 아니다.
좀 더 자세히 알고 싶다면 유튜브 얄코님의 JS 비동기 프로그래밍에 관한 영상을 추천한다.
다시 위의 예제를 살펴보자.
const important = function() {
console.log('중요한 작업')
}
setTimeout(() => {
console.log('중요하지 않은 작업')
}, 1000);
important();
JavaScript 엔진은 위에서 아래로 순차적으로 코드를 실행시킨다. 동작 과정을 자세히 보자.
- 엔진이 setTimout 이라는 특정 시간이 소모되는 함수를 발견하고 1000ms(1초) 의 타이머가 발생한다.
- 이와 동시에 엔진은 동기적으로 이어서 important() 를 실행시킨다.
- '중요한 작업' 이 콘솔창에 출력된다.
- 1초후 타이머가 종료되면 console.log('중요하지 않은 작업') 를 실행시키는 코드 (정확히 콜백함수)가 Callback Queue에 들어간다.
- Event Loop는 동기적 작업이 모두 마무리 되면(정확히 콜스택이 비워지면) Callback Queue에 있는 console.log('중요하지 않은 작업')를 엔진으로(콜스택으로) 보낸다.
- 엔진은 받은 console.log 함수를 실행한다.
- '중요하지 않은 작업' 이 콘솔창에 출력된다.
그렇다면 왜 런타임은 비동기 실행을 하는 것일까 ?
이유는 간단하다.
우리가 웹페이지를 방문하면 브라우저는 서버와 실시간으로 많은 데이터를 주고 받는다.
이때, 사용자 입장에서는 브라우저가 서버와 데이터를 주고 받든 말든 빠르게 웹페이지가 보여지길 원한다.
만약, 코드가 동기적으로만 실행된다면 우리는 서버가 보낸 데이터를 다 받을 때까지 기다리고
모든 작업이 완료된 후에야 웹페이지를 볼 수 있을 것이다.
따라서, 시간이 걸리는 작업은 비동기적으로 실행하여 따로 처리를 하고
웹페이지를 보여주는 작업은 동기적으로 바로 실행하는 것이다.
하지만, 이러한 비동기를 정확히 처리하지 못하면 내가 원하는 대로
실행 결과가 나오지 않을 수 있다.
특히나 비동기적인 복잡한 작업을 많이 할수록 해당 문제가 발생한다.
다음에는 비동기의 처리 방식인 콜백함수, Promise, aysnc await 에 대하여 순차적으로 알아보겠다.
[JavaScript] 콜백 함수와(Callback) 비동기(Asyncronous) 처리
흔히 JavaScript의 비동기를 처리하는 방법은 크게 세 가지가 있다. 1. 콜백 함수(Callback)2. Promise3. async awiat 이 중에서 가장 기본이 되는 콜백 함수에 대해서 알아보자. 먼저, 비동기를 어떻게 콜백
junhee1203.tistory.com
끝으로 예시에서는 비동기 작업을 중요하지 않은 작업, 동기 작업을 중요한 작업으로 비유를 들었다.
하지만 예시일 뿐이지, 어떠한 작업이 더 중요하다 그런 것은 없다.
서버에서 데이터를 받아오는 비동기 작업도 웹페이지를 구성할 때 매우 중요한 작업이다.
단지, 비동기의 이해를 위한 비유일 뿐이다.
'JavaScript' 카테고리의 다른 글
[JavaScript] drag (드래그) 관련 이벤트 전격 분석 (0) | 2024.09.10 |
---|---|
[JavaScript] Element.closest 메소드 분석 (0) | 2024.08.31 |
[JavaScript] 이벤트 버블링(Bubbling), 이벤트 위임(Delegation) (2) | 2024.08.31 |
[JavaScript] 모듈 시스템 CommonJS vs ES6 비교 (0) | 2024.08.25 |
[JavaScript] 콜백 함수와(Callback) 비동기(Asyncronous) 처리 (0) | 2024.08.11 |