요즘 OS수업을 듣고 있는데, 프로세스 스케줄링 관련 부분을 배우고 있다.
그러다가 문득, 최근 면접에서 받은, 그리고 제대로 답하지 못했던 이벤트루프는 왜 그런 우선순위로 작업을 실행시킬까? 라는 질문이 떠올라 지금에라도 작성해본다.
서론
동시성
Concurrency(동시성)와 Parallelism(병렬성)
동시성과 병렬성은 같은 단어 같지만, 분명히 다른 단어이다.
동시성은 동시에 실행되는 것 같이 보이는것
병렬성은 실제로 여러 작업이 동시에 처리되는 것이다.
동시성에 대해 말할 것이다.
과거, 코어가 하나인 CPU에서 동시성을 해결하는 것은 매우 중요했다.
안그러면, 하나의 프로세스만 돌릴 수 있을테니까.
JS도 마찬가지다.
Javascript는 싱글 스레드이다. 코어가 하나인 CPU인 것이다.
여기서 우리는 로직 수행도 하고, 네트워크 요청도하고, UI업데이트 명령도 내려야한다.
그리고 모든 작업이 동시에 실행되는 것처럼 보여야한다.
이 두가지 요소를 우선순위라는 같은 관점에서 바라보고, 왜 이렇게 설계되었는지 나름의 답변을 내고자한다.
스케줄링, 우선순위
그래서 CPU는 시분할로 작업을 작게 쪼개고, 이걸 작업을 할당해서 순환시켜가며 실행하는 방식으로 동시성을 제어했다.
이걸 어떻게 할당하냐?가 바로 스케줄링이다.
동시성을 구현하기 위해서는 아이러니하게도 순서가 중요하다.
이때 더 빨리 실행될 필요가 있는 것들, 더 늦게 실행되도 되는 것들, 그리고 총 실행 시간, 스위칭 시간 등등... 수많은 목적들을 고려해서, 목적에 맞게 만들 수 있다.
우선순위에 대해 한가지 예시를 살펴보자.
한가지 예로 time slice형태의 스케줄링에서 throughput을 개선시키기위해서는 CPU Intensive 프로세스와 I/O Intensive 프로세스에 따른 우선순위를 고려할 수 있다.
CPU Intensive 프로세스는 CPU사용률이 높고, I/O 작업량이 적은 프로세스다.
I/O Intensive 프로세스는 CPU사용률이 낮고, I/O 작업량이 많은 프로세스다.
I/O Intensive한 프로세스는 CPU에서 많이 작업될 필요가 없다.
그렇다면 I/O Intensive한 프로세스는 CPU에 할당되어도 빨리 Block상태가된다.
즉 context switch를 줄일 수 있고, 전반적인 대기시간을 줄일 있어 throughput을 증대시킬 수 있다.
따라서 먼저 실행해버리는게 좋을 수 있다. 따라서 이런 관점에서, 우선순위가 높아진다
반대로 CPU Intensive한 프로세스는 CPU에서 많이 작업되어야한다.
그렇다면 이 프로세스는 CPU에서 많이 실행되어야한다. 이건 I/O Intensive한 프로세스의 대기시간을 늘려 throughput을 악화시킬 수 있다. 따라서 우선순위가 낮아진다.
이처럼 어떤 목적에 따라 만들어진 것인가?를 이해하면 우선순위를 이해할 수 있다.
본론
Microtask, Macrotask, rAF
먼저 우선순위를 작성하자면
microtask > rAF >macrotask가되겠다.
왜 그럼 이런 우선순위가 나오게된걸까?
왜 이런 우선순위가 나오게된걸까
왜 그런가에 대해 생각해보려면, 이 관점에서 브라우저가 무슨 일을 주로 하는지 생각해볼 필요가 있다.
CPU스케줄링처럼 동시성을 구현함으로써 얻을 목적을 이해할 필요가 있다.
브라우저는 정말 다양한 일들을 해주지만, 여기서 살펴보아야 할 것은 아래 두 가지일 것이다.
- 사용자에게 완성된 화면을 보여주어야한다.
- 초기에 완성된 화면을 업데이트하여 다시 보여주어야한다.
또한, javascript가 실행되는 경우이므로, 1번도 제외된다.
즉, 이 관점에서 브라우저가 하는 가장 중요한 할 일은 업데이트된 화면을 보여주는 것이다.
그리고 세가지 작업의 종류를 간단하게 살펴보자.
Microtask Queue
Promise와 MutationObserver가 이 작업에 속한다.
requestAnimationFrame
request Animation Frame은 브라우저가 리페인트 바로 전에, 애니메이션을 업데이트할 지정될 함수를 호출하도록 요청하는 것이다.
MacrotaskQueue
setTimeout, setInterval이 이에 속한다.
중요한 것을 생각해보기
여기서 사용자의 액션에 따라 업데이트된 화면을 보여줄때 가장 중요한 것은 무엇일까?
보여준다는 부분에서 requestAnimationFrame이 가장 우선순위가 높을 것 같다.
하지만, 업데이트 된에 집중해보자.
그리고 "업데이트"와 "된" 두 가지를 고려해서 생각해보자.
업데이트
화면의 업데이트는 count값을 올리는 것처럼 동기적일 수도 있지만, 서버 데이터를 받아오는 것처럼 비동기적인 작업이 될 수 도 있다.
된
되었다라는 것은 작업이 끝났다는 것이다.
즉, 가장 최신의 데이터를 가져오는 것을 끝내고, 화면을 그려야(업데이트해야) 사용자는 문제없이 제대로된 데이터를 보게될 것이다.
그런데 만약 업데이트의 우선순위보다 그리는 것의 우선순위가 더 높다면?
비동기 업데이트의 경우, 데이터가 최신이 아닌데도, 브라우저는 화면을 그리게 될 것이고, 그럼 유저가 보는 화면에는 문제가 생길 것이다.
그럼 화면을 한번 더 그려야, 제대로 보이게 될 것이다. 이건 효율적이지도 않고, 제대로 작동하지 않을 가능성이 있다.
따라서 그리는 것(requestAnimationFrame)보다 업데이트(microtask)의 우선순위가 높아진다.
또한 자연스럽게 그리는것도, 업데이트라고 보기도 어려운 MacroTask는 자연스럽게 우선순위가 낮아지게된다.
결론
업데이트된 것을 보여준다 라는 목적에 의해,
업데이트된을 담당하는 microtask의 우선순위가 가장 높고,
보여준다 를 담당한다 볼 수 있는 requestAnimationFrame의 우선순위가 다음이고,
이런 목적과 조금 거리가 있는 macrotask의 우선순위가 가장 낮다.
결론으로부터 비롯되는 몇가지 사실
그렇다면
- 무한히 많은 microtask가 있다면 macrotask는 starve상태에 빠질 수 있다.
- 무한한 재귀를 가진 requestAnimationFrame이 있어도, repaint시점 직전에 1회 실행되는 것이 보장된다.따라서 requestAnimationFrame은 macrotask를 block시킬 수 없다.