프로세스와 쓰레드란?(선행지식)
우선 선행되어야할 개념이있습니다. 바로 Process & Thread 입니다
Process 란? 운영체제 위에서 연속적으로 실행되고있는 것을 말합니다
프로세스들은 각각 독립적으로 실행되며, 어느 한 프로세스가 오류로 인해 죽는다고해도 다른 프로세스에는 영향이가지 않습니다. 마치 멜론 프로세스가 버그로인해 꺼졌다고, 잘 굴러가던 크롬 브라우저가 꺼지지 않는것 처럼요
Process는 각각 할당된 한정된 자원이있습니다
그림과같이 code, stack, heap, data 4개의 자원(Resource)로 구성되어있습니다
code는 말그대로 프로그래머가 작성한 코드를 말합니다. javascript, python, java 등 이 될수있겠지요
stack은 코드를 한줄, 한줄 읽으며 실행해야할 함수들이 쌓이는 곳입니다
heap은 코드에서 선언한 변수,객체,함수등 동적으로 할당된 변수들이 저장되는 공간이고
data는 코드에서 전역변수나, static 변수들이 저장되는 곳입니다
이제부터 쓰레드 라는개념이 나옵니다
쓰레드는 한 프로세스 안에서 여러개가 동작할 수 있습니다
각 쓰레드는 해야할 동작을 가지고있고, 그렇기 때문에 저마다의 Stack을 가지고 있습니다. Stack이 두개라는것은 두개의 별개의 코드들이 동시에 실행된다는 말이겠죠?
그럼과 동시에, 별도의 동작이지만 한 프로세스 내에서 동작하는 것임으로 Code, Heap, Data 자원은 공유를 하게됩니다
예를들면, 한프로그램내에서 음악도 틀어놓고, 웹서핑도 가능하고, 영상편집도 가능하게 하는 방법이 바로 Multithreading 입니다
Stack의 경우에 조금더 자세하게 설명하자면, 함수가 실행되고 되돌아가야하는 분기점들을 기억하고있습니다. 그래서 여러개의 함수가 실행되어도, 실행했던 함수가 끝나면, 다시 원점으로 돌아와 다음함수를 실행하게 되는것이죠. 각 Thead는 자신만의 흐름을 가지고있습니다. 그런데 이와 반대로 Code, Heap, Data 들은 각각의 Thread들이 실행되면서 공유되는 자원들입니다. 이 자원들을 수십개의 Thread가 같이 사용을하게되는데, 만약 공통된 자원을 참조하고,업데이트했을때 문제가 발생할 수있습니다
Deep한 내용(javascript에서는 멀티쓰레딩을 지원하지않고, 직접 조작할 일이 없기 때문에 이해가안되신다면 넘어가셔도 됩니다)
예를들어 score = 0; 이라는 변수가있고, 쓰레드 두개가 1초에 1씩 증가시키면서 100까지 만든다고 하겠습니다. 그럼 총 시간이 얼마나 걸릴까요? 상식적으로 50초가 걸리지않을까요? 쓰레드 두개가 1초에 1씩 증가시키니, 1초에 2씩 증가되는 것입니다. 하지만 50초가 넘게걸릴수도, 적게걸릴 수도있습니다. 왜냐하면 각 쓰레드는 값을 참조해서 증가시키고 업데이트하는 과정을 반복합니다. 그런데 값을 참조하는 순간이 완전 일치한다면 어떻게될까요? 처음으로 예를들면 현재 score가 0일때, 첫번째 쓰레드는 0을가져가서 1로 업데이트하고, 두번째 쓰레드는 1을 가져가서 2로 업데이트해야하는데, 동시에 참조하게되면서 둗라 0을가져가서 둘다1로 업데이트를 한다면 멀티쓰레딩의 효과가 나지않겠죠. 또 단순한 숫자 카운팅이 아니라 중요한 동작을한다면 치명적 오류가 발생할 수 있습니다. 이를 임계영역(Critical Section) 이라고 합니다. C언어로 코딩을 해보면 어떻게 동작하는지 바로 알수 있을것입니다. 이를 방지하기위해서 쓰레드의 작업처리순서나, 공유자원에대한 접근을 컨트롤 해주어야합니다. Java 에서는 synchronized 를 사용하기도 합니다. 혹인 mutex, 세마포어 등 의 방법도있습니다. 이들은 공유자원을 동기화시켜주는 역할을 하는데, 이 동기화작업이 과도하게이루어질경우, 적절하지 못할경우네는 context switching 발생이 잦아지면서, 오히려 단일 쓰레드 모다 못한 성능이 나올 수 도 있습니다.
Javascript 는 Single Threaded 이다
자바 스크립트는 single thread 입니다. multithreading을 지원하지않기 때문에, 앞서 설명했던 동시다발적인 작업을 할 수없습니다
하지만 javascript가 동작하고있는 브라우저에서는 여러 쓰레드가 들어있고, 그것들이 바로 webAPIs 입니다. 이친구들의 힘을 빌려 Multithreading을 할수있게되고, Event Loop라는 것을 통해 더 다양한 동작들도 가능하게 됩니다.
우리가 작성한 javascript 코드들은 브라우저에서 실행되고 브라우저는 Javascript를 실행시키는 Javascript Engine (V8 Engine)으로 javascript 해석합니다.
자바스크립트 엔진에도 Process와 같이 Stack과 Heap 이 존재하는데 여기선 Call Stack, Memory Heap이라고 합니다
Memory Heap 에는 우리가 선언,할당한 모든 변수,함수,오브젝트들이 저장되고
Call Stack은 실행될 함수들이 쌓이는 곳입니다
*Stack은 자료구조중 하나로써 LIFO : Last In First Out 구조를 가지고있습니다. 가장 마지막에 읽은것 순서대로 실행되는 것이죠
만약 이 Stack 이 잘못동작하게 된다면 어떤경우일까요?
바로 함수가 자기자신을 호출하는 경우입니다. 이를 재귀함수라고하는데, 계속 자기자신을 호출함으로써, Call Stack에는 자기자신이 무한대로 쌓이게되고, Chrome의 경우 Call Stack의 한계가 약 12만개로 알려져있어, 그 한도치를 도달하면 브라우저가 죽게됩니다
재귀함수는 유용하지만, 잘못쓰면 이렇게 됩니다.
Browser Runtime Environment
자바스크립트 런타임환경만으로는 할수있는 것이 너무나도 한정적입니다. 하지만 브라우저위에서 돌아가는 자바스크립트는 다르죠
바로 webAPIs 를 사용할 수있기 때문입니다.
그렇다면 앞서 설명한 javascript engine은 코드를 읽어서 call stack에 쌓아서 실행한다고했는데, webAPI 를 이용해서 등록한 callback함수는 어떻게 동작을 하게될까요?
바로 Task Queue를 통해서 Call Stack에 해당 callback 함수가 전달되게 됩니다.
전달되는 조건은 각 API마다 다릅니다. setTimeout()을 예로들면 지정한 시간이 지나면 함께 전달되었던 callback함수를 Task Queue에 전달하게되고, Task Queue는 Call Stack이 비어있을 경우에 callback 함수가 들어온 순서대로 Call Stack으로 전달합니다
Task Queue의 특징은 Call Stack이 비어있을 경우에 전달한다는 것입니다
지금같은 경우 timeout callback이 call stack에서 완료되지않았기 때문에 Task Queue에 클릭이벤트에 대한 callback이 쌓이고있습니다.
timeout callback이 완료가 되고서야 쌓였던 callback함수가 call stack으로 이동합니다. 여기서 중요한점은 한번에 하나씩 이동된다는 것입니다.
Render 와 Microtask Queue
조금더 자세하게 들어가면 WebAPIs를 처리하는곳이 Task Queue만 있는것은 아닙니다
Promise의 then과 mutation observer를 처리하는 Microtask Queue와 웹페이지를 업데이트(렌더링)처리를 하는 Render가 있습니다
Request Animation Frame은 브라우저가 업데이트되기전에 실행하는 callback함수를 저장하고 실행하는 곳입니다
이 3가지를 Event Loop가 웹페이지가 종료될때까지 시계방향으로 순회하면서 작업을 처리하게됩니다
Call Stack -> Render -> Microtask Queue -> Task Queue 순서대로가는데
Call Stack에서 WebAPIs를 호출하지않으면 Event Loop가 Call Stack에 머물러있게됩니다(당연하게도)
그런데 어느순간 WebAPIs중에 한 함수를 호출하면 event Loop가 제일먼저 Render 로 이동합니다(이말은 즉, webAPIs가 호출되면 화면이 가장먼저 업데이트된다는 소리입니다)
이때 Request Animation Frame을 첫번째로 검사합니다. 이 함수로 호출된 callback들을 가장먼저 처리한다음 Render Tree를 만들고 Layout을 계산하고 Paint 하게됩니다.
두번째로 Microtask Queue를 검사합니다. 여기엔 Promise에서 resolve된 callback함수들이 쌓여있습니다. Microtask Queue는 Task Queue와는 다르게, 쌓여있는 모든 callback함수들이 다 처리가되면 event loop를 놓아줍니다. 만약, callback을 처리하는 와중에 또 promise가 쌓이게된다면 그것도 다 처리한다음 event loop가 이동합니다
마지막으로 Task Queue입니다. 여기서는 callback함수들을 하나를 Call Stack에 전달하고 넘어갑니다
이런 흐름을 이해하고있다면 의문적으로 동작하는 코드들을 잘 해석할 수 있을 것입니다!
'Archive' 카테고리의 다른 글
Babel 이란 + 핵심사용법 (0) | 2022.01.15 |
---|---|
Javascript Module System(모듈 시스템) (0) | 2022.01.15 |
JAVA로 안드로이드 딱 빌드까지만 해보기 (0) | 2021.12.30 |
onMount() (0) | 2021.11.14 |
[JS] if,for문을 줄여주는 built-in 함수들 (0) | 2021.11.14 |