멀티 스레딩
이란 실행 중인 프로그램인 하나의 프로세스
를 여러 개의 작업 단위로 구성하고, 여러 개의 스레드를 생성해 각 스레드
에게 하나씩 작업을 할당하는 작업 처리 방식입니다.
멀티 스레딩은 시스템 자원 소모 감소, 시스템 처리량 증가, 간단한 통신 방법 등 다양한 장점으로 인해 효율성이 요구되는 작업에 자주 사용됩니다.
IPC
)에는 파이프, 파일, 소켓 등을 이용합니다만, 스레드는 공유 메모리를 사용하므로 통신 시간도 단축되고 통신 관련 자원 소모가 적습니다.하지만 동시에, 동작 방식이 복잡해 디버깅이 까다로우며 자원 공유의 (동기화
) 문제 등이 발생합니다.
동기화 문제는 Semaphore와 Mutex로 대표되는 임계구역(Critical Section
)과 관련된 문제인데, 아래에서 다뤄보겠습니다.
미션에서 제시된 프로세스 스케줄링 방식을 다시 한번 정리해 보겠습니다.
여러 자료를 보면 확인할 수 있듯이, 스레드
스케줄링도 프로세스
스케줄링과 완전히 똑같은 전략들을 이용할 수 있습니다. 위의 내용에서 프로세스
를 스레드로
만 바꿔주면 모두 매치됩니다.
Scheduling Priorities - Win32 apps
Mach Scheduling and Thread Interfaces
FIFO, Round-Robin 등 다양한 전략을 제공하는 듯 합니다만, 일반적으로 Round-Robin을 사용한다는 것 같습니다. 정확히는 찾기가 힘드네요.
스케줄링 메커니즘을 따로 구현하진 않았지만, Node.js의 동작 원리를 통해 확인할 수 있듯이, 싱글 스레드 언어
인 JS를 보완하기 위해 libuv
가 비동기, 동시성, 논블로킹을 구현해준다고 합니다.
결론적으로 node에서 제공하는 worker_thread 모듈을 이용한 저는 libuv
에서 제공하는 **Round-Robin **방식으로 구현된 것으로 해석할 수 있겠습니다.
하지만 결과적으로 같은 페이즈 안에서는 FIFO 순서로 콜백 함수를 처리한다고 하네요. timer 페이즈가 그 중 하나이므로 FIFO가 아닐까 예상됩니다.
명시적으로 나와있지 않아 시간을 내서 Node.js의 내부 구조를 더 상세히 파악해봐야 겠습니다.
🔄 자바스크립트 이벤트 루프 동작 구조 & 원리 끝판왕
JS는 setTimeout
과 같이 비동기로 동작하는 함수를 이벤트 루프
라는 데서 관리합니다.
이벤트 루프는 다음의 과정으로 동작합니다.
setTimeout(() => {
console.log("> done");
}, 1000);
foo();
setTimeout()
이 호출되면 콜 스택
에 쌓입니다.setTimeout()
의 매개변수에 할당된 콜백 함수 ()⇒{console.log(”> done”);}
가 Timer Web API
에 전달됩니다.Timer Web API
는 백그라운드로 1000ms(1초)를 셉니다.이벤트 루프
는 Time Web API
가 가지고 있던 콜백 함수 ()⇒{console.log(”> done”);}
를 다시 Task Queue
로 옮깁니다.Call Stack
에서는 열심히 setTimeout() 아래의 함수(foo()
)들이 실행됩니다. Call Stack이 완전히 빌 때 까지!Call Stack
이 비어있는 경우를 탐지하여, Task Queue
에 있는 콜백 함수 ()⇒{console.log(”> done”);}
를 다시 Call Stack으로 옮깁니다.Call Stack
에서는 콜백 함수 ()⇒{console.log(”> done”);}
를 실행하게 되고, 콘솔창에 > done이 출력됩니다.정말 정말 길고 복잡해서 머리가 아프긴 하지만, 미션 해결 과정에서 Promise를 만들고 무한 루프로 값 변화를 기다렸을 때 도대체 왜 값이 변하지 않는지를 드디어 이해했습니다!!!
이후 코드가 다 실행되어야 콜백이 실행되는거였구나…
엄청난 깨달음입니다. async
/await
.. 너무 머리아프지만 꼭 배워야하는거였군요.
javaScript(타이머함수-setTimeout, setInterval, clearTimeout, clearInterval)
대표적으로 setInterval()
이 있습니다. setTimeout()과 달리 시간 간격마다 함수를 실행합니다.
setTimeout(함수, 시간)
: 일정 시간 후 함수 실행setInterval(함수, 시간)
: 시간 간격마다 함수 실행clearTimeout()
: 설정된 Timeout 함수를 종료clearInterval()
: 설정된 Interval 함수를 종료Window: requestAnimationFrame() method - Web API | MDN
또 JS로 게임이나 애니메이션을 만들 때 대표적으로 사용되는 requestAnimationFrame()
이 있습니다. 이 함수는 사용 방법이 좀 독특합니다.
function animationLoop() {
// animation-related code
requestAnimationFrame(animationLoop);
}
// start off our animation loop!
animationLoop();
위와 같이 animationLoop()
를 만들면, 무한 재귀 호출인 것 같지만 브라우저에서 이 부분이 특별히 처리되어 로컬 PC의 초당 프레임 수
에 맞게 루프를 반복합니다.
이를 통해 크롬 공룡게임 같은 다양한 게임을 만들 수 있습니다! ㅎ 관련해서 제가 좋아하는 코딩애플님의 영상을 또 첨부해놓겠습니다.
requestAnimationFrame
의 사용 방법이 쉽게 소개되어 있습니다.
현재 시간을 가져올 수 있는 .now() 함수를 이용해서 타이머를 만들수있다!
타임스탬프를 찍고, 몇초 지나면 그 값을 비교해서
경쟁상태, 임계영역의 개념과 동기화를 위한 여러 상호배제 기법 (mutex, semaphore, monitor)
임계구역 또는 임계영역(Critical Section
)은 한 개의 연산을 둘 이상의 동시에 호출된 쓰레드가 하나의 공유 자원에 접근해서 문제가 발생하는 코드 영역 또는 함수를 말합니다.
이런 코드가 실행되면 둘 이상의 입력이나 조작의 실행 순서에 따라 결과가 달라지게 되는데, 이를 경쟁 상태(Race Condition
)라고 합니다.
경쟁 상태 중 특히 둘 이상의 스레드가 서로 점유하는 자원을 요구하며 무한정 기다리는 현상을 교착 상태(Dead Lock
)라고 합니다.
교착 상태가 일어나면 프로그램이 멈춰버리므로 절대 일어나지 않도록 예방, 회피, 탐지, 복구 등으로 해결해줘야 합니다.
다음 두 개념(세마포어와 뮤텍스)는 교착 상태를 방지하는 대표적인 해결 방법입니다.
세마포어
는 공유 자원에 접근할 수 있는 최대 허용치만큼 동시 사용자(쓰레드, 프로세스)의 접근을 허용하게 하는 전략입니다.
뮤텍스
는 Key에 해당하는 어떠한 오브젝트를 두고, 이 오브젝트를 소유한 사용자(쓰레드, 프로세스)만이 공유자원에 접근할 수 있도록 통제하는 전략입니다.
Mutual Exclusion의 줄임말로, 상호 배제라는 뜻입니다.
장단점도 있고, 둘 다 완벽한 개념이 아니기 때문에 상황에 맞게 두 개념 모두 적절히 변환해서 사용하는 것을 추천한다고 합니다.
[Philosophers] 예시/예제로 보는 뮤텍스와 세마포어의 차이
주로 뮤텍스
는 같은 종류의 공유 자원이 하나일 때, 세마포어
는 여러 개일 때 사용하면 되겠습니다.
비유하자면, 뮤텍스
는 화장실이 하나라 열쇠를 입구에 걸어둔 식당, 세마포어
는 화장실이 여러 개라 입구전광판에 빈 칸의 개수가 적혀있는 식당이라네요. 이해 쏙쏙?