[Backend] 멀티쓰레드와 비동기 IO에 대해

2 minute read

좋은 글을 발견해서 일단 copy paste만 합니다.

멀티스레딩 프로그래을 다른 구분으로 나누면 Computed-Bounded 스레드와 IO-Bounded 스레드로 나뉠 수 있습니다.

Computed-Bounded 스레드 라 하면 CPU, CPU Cache, 메인 메모리 범위에서 수행되는 작업입니다. 보통의 수학 연산이나, 메모리상의 Data 처리에 해당됩니다.

IO-Bounded 스레드라고 하다면? 당근 CPU, 메모리를 제외한 HDD, 네트웍과 같이 외부저장장치와의 통신 작업을 수행하는 스레드입니다.

예를 들어 파일 내용을 읽어오는 작업을 스레드를 만들어서 한다면, 당연히 백그라운드 스레드에서 돌게 하므로, 메인 스레드에서는 다른 작업을 수행 할 수 있으므로, 아주 효과적인 프로그램을 만들 수 있습니다. 여기까지는 IO-Bound 스레드

다음으로 비동기 IO 방식으로 작업을 수행한다면 이렇게 됩니다.

  1. 백그라운드 스레드를 생성합니다.
  2. 비동기 IO 명령을 통해 읽기를 요청합니다. -> 비동기결과를 받기 위한 토큰을 받던가 (APM), 완료이벤트를 받던가(EAP)
  3. 스레드가 죽습니다.

……

  1. 완료되었음을 통지 받습니다. (토큰 또는 이벤트로) == 새로운 스레드가 만들어집니다.
  2. 실제 Data를 읽습니다.
  3. 스레드를 종료합니다.

자 여기서 비동기 IO 방식이 IO-Bounded 멀티스레드와 다른 점은 전체 작업이 이루어질 동안 스레드가 살아있느냐, 실제 작업할 때문 스레드가 살아있느냐의 차이입니다.

그럼 이게 뭐가 문제냐? Windows 에서는 Thread Context Switching 부하 때문입니다. Thread 를 여러개 만드는 것보다 ThreadPool과 Worker Thread 를 사용하는 것이 더 효과적이라는 것은 이미 알려졌듯이 이 방식은 Thread Context Switching 부하를 최대한 감소해보자는 의도에서 나온것입니다.

자 그럼 위의 3 ~ 4 동안이 1~6중에 미미한 시간을 차지할 수도 있고, 대부분의 시간을 차지할 수도 있습니다. HDD가 아닌 Network을 통한 DB의 정보를 가져오는 예라면 당연히 3 ~ 4 구간은 거의 99%를 차지하겠지요?

이 이야기는 99% 노는 스레드를 쓸데없이 살려놔서, OS로 하여금 Thread Context Switching 부하만 발생시키게 한다는 뜻입니다. 이렇게 노는 스레드가 많아지면, 시스템이 느려지고, 작업 처리 속도 또한 느려지니, 멀티스레드 프로그래밍은 복잡하고, 성능은 나오지 않는다라고 선입견을 가질 수 있습니다.

싱글 쓰레드와 이벤트 기반의 비동기 I/O 처리 노드는 이러한 문제들을 싱글 쓰레드와 이벤트 기반의 비동기 I/O 처리로 해결하고 그 성능을 끌어올릴 수 있도록 하는 비동기 프로그래밍 모델을 제공해주고 있습니다.

싱글 쓰레드를 가진 노드는 I/O 작업이 시작되면 I/O 작업 처리에 대한 응답을 기다리지 않고, 바로 다음 작업을 실행해버립니다. 대신 I/O 작업이 종료되면 이벤트를 발생시키고, 이 이벤트는 해당 프로세스의 이벤트 큐에 등록되게 됩니다. 노드로 개발된 프로세스는 이 이벤트 큐에 등록된 새로운 이벤트를 감지하여, 해당 이벤트 시 수행하여야 할 작업을 실행하게 됩니다.

멀티스레드하다가 왜 비동기 IO 까지 갔냐면… 멀티스레드를 해도 별 효과 없는 부분이 IO 작업이고, 이 부분을 극복할 방법은 비동기 프로그래밍밖에 없다는 결론이 나왔거든요…

결론적으로 멀티스레드는 여러 분야에서 꼭 필요한 분야이고, 어렵지만 익혀야 할 기법이라고 생각합니다. 여기에 비해 비동기 IO 에 대해서는 제공되는 API 가 있다면 그걸 적극적으로 사용하세요. 굳이 네트웍 소켓 프로그램을 자신이 만들겠다고 덤비지 마시고^^ 주어진 비동기 IO API만 잘 사용해도, 전체 시스템의 처리량이 늘고, 확장성이 좋아진다고 장담합니다.^^

이벤트 루프 이벤트 루프(Event Loop)라는 것은 작업을 요청하면서 그 작업이 완료되었을 때 어떤 작업을 진행할지에 대한 콜백 함수를 지정하여 동작이 완료되었을 때 해당 콜백 함수를 실행되는 방식의 동작 방식을 말합니다.

만약 클라이언트가 웹 서버에 HTTP 형식으로 요청하게 되면 서버에서는 이벤트 루프가 계속 돌고 있다가 이를 감지하고 알맞은 작업을 워커 쓰레드를 생성하여 실행합니다. 이때 이벤트 루프는 해당 워커 쓰레드가 작업을 마친 뒤 그 결과와 함께 응답할 때까지 기다리는 것이 아니라 바로 루프로 복귀하여 다른 요청을 기다리게 됩니다.

다시 말해 이벤트 루프는 어떤 요청이 발생하면 그 작업에 대해 쓰레드 실행만을 일으킬 뿐입니다. 이후 작업을 할당받았던 해당 쓰레드가 모든 작업을 마치면 미리 전달받은 콜백 함수를 실행하도록 이벤트 루프로 응답하게 되며 이벤트 루프는 이것을 실행하여 클라이언트에게 결과를 응답해줍니다.

Reference