1. Asynchronous I/O
2. Asynchronous Programming Under Linux
- 우리는 대부분의 애플리케이션이 클라우드에 존재하는 시대에 살고 있습니다.
- 사용자가 클라우드 기반 애플리케이션에 연결할 때마 다, 웹 프레임워크 위에 작성된 대부분의 비즈니스 로직이 실행됩니다.
- 모든 요청은 별도의 프로세스, 별도의 스레드에서 처리되거나, 비동기 프로그램에서는 여러 요청이 동일한 프로세스에서 처리됩니다.
- 오늘날 스레드 풀과 비동기 모델 기반의 애플리케이션 프레임워크는 동등하게 인기가 있습니다.
- 이러한 애플리케이션들은 작업을 수행하는 과정에서 네트워킹 및 파일 관련 시스템 호출을 혼합하여 사용합니다.
2.1 Processes (프로세스)
- 일반적으로 read와 같은 시스템 호출을 하면, 파일이 읽혀지고 데이터가 가용해질 때까지 프로그램이 블록됩니다.
- 이는 일반적으로 꽤 빠르게 처리되어 프로그램이 블록되고 있다는 것을 보통 인식하지 못합니다.
- 하지만 아마도 당신은 특히 바쁜 기계에서 당신의 프로그램이 초당 수백 번씩 다른 프로그램을 실행하기 위해 CPU에서 전환되었다는 사실도 인식하지 못할 것입니다.
- 시스템 호출이 블록되면, 커널 모드에서 실행 중인 시스템 호출이 반환될 때마다 프로그램이 언블록되어 계속 실행됩니다.
- 대부분의 다른 프로그램과 같다면, 운영 체제로부터 무언가가 필요할 때마다 이러한 블록 및 언블록 사이클을 계속할 것입니다.
- 이 패러다임은 이벤트가 논리적 순서대로 하나씩 발생하기 때문에 이해하기 쉽습니다
- 비록 프로그램이 다른 프로그램을 실행하기 위해 선점되거나 시스템 호출에 의해 블록될 수 있더라도.
- 프로그램이 다른 프로그램을 실행하기 위해 선점된다는 사실을 무시하면, 프로그램이 순차적으로 로직을 실행하는 것처럼 보입니다.
2.2 Multi-threaded programs (다중 스레드 프로그램)
- 다중 스레드 프로그램에서는 이 정신적 모델이 매우 잘 확장됩니다.
- 프로그램에는 많은 실행 스레드가 있습니다.
- 이러한 인스턴스들은 동일한 로직의 인스턴스(클라이언트 요청을 처리하기 위해 생성된 스레드의 인스턴스)이거나 그렇지 않을 수도 있습니다(임시 파일을 정리하기 위해 항상 백그라운드에서 실행되는 전용 스레드).
- 이러한 개별 스레드는 시스템 호출에 의해 선점되거나 블록 및 언블록됩니다.
- 몇 개 또는 여러 개가 실행 중이지만, 이 정신적 모델도 상당히 확장 가능합니다.
- 그러나 다중 스레드 여정에서 락(locks)과 뮤텍스(mutexes)와 같은 까다로운 것들을 만나게 될 것입니다.
- 하지만 우리의 논의를 위해, 우리는 가장 편리하게 그것들을 무시할 것입니다.
2.3 Why asynchronous programming? (왜 비동기 프로그래밍인가?)
- 시간당 수천 또는 수십만 개의 요청을 처리하는 것을 구축하려는 경우, 비동기 I/O에 신경 쓸 필요가 없습니다.
- 스레드 풀 기반 아키텍처 주변에 설계된 애플리케이션 프레임워크가 충분히 잘 작동할 것입니다.
- 그러나 시간당 수백만 개의 요청을 효율적으로 처리하고 효율성에 신경 쓴다면, 비동기 프로그래밍을 더 자세히 살펴볼 필요가 있습니다.
- 비동기 프로그래밍은 단일 스레드에서 대부분의 I/O를 처리함으로써 운영 체제의 스레드/프로세스 컨텍스트 전환 오버헤드를 피합니다.
- 운영 체제의 컨텍스트 전환 오버헤드는 많지 않은 것처럼 보일 수 있지만, 상당한 규모와 동시성을 다룰 때 중요해지기 시작합니다.
- 다음 그림은 1초 내에 일련의 요청에서 무슨 일이 일어나는지를 보여줍니다.
- 스레드는 블록된 상태에서 실행 상태로 이동합니다.
- 단일 스레드 및 다중 스레드 앱에서 무슨 일이 일어나는지는 명확하지만, 비동기 프로그래밍이 어떻게 작동하는지는 로켓 과학은 아니지만 이해하기 조금 까다로울 수 있습니다.
- 아래 그림이 이해하는 데 도움이 되길 바랍니다.

2.3.1 리눅스 프로세스 모델 비교
- 반복적(Iterative)
- 이 서버 유형은 한 번에 하나의 요청을 처리합니다.
- 한 요청을 처리하는 동안, 다른 요청들은 이전 요청의 처리가 완료될 때까지 기다려야 합니다.
- 운영 체제가 대기열에 넣을 수 있는 요청 수에는 제한이 있습니다.
- 기본적으로 리눅스는 5.4 미만의 커널 버전에서는 최대 128개, 그 이상의 버전에서는 4,096개까지 대기열에 넣습니다.
- 포킹(Forking)
- 이 유형의 서버는 처리해야 할 각 요청마다 새로운 프로세스를 생성합니다.
- 이렇게 하면 요청들이 이전 요청의 처리를 기다릴 필요가 없습니다.
- 서로 다른 프로세스가 다른 요청을 처리합니다.
- 또한 많은 프로세스나 스레드가 작동할 때, 이들은 여러 개의 사용 가능한 CPU 코어를 활용하는 경향이 있습니다.
- 프리포크(Preforked)
- 이 유형의 서버는 요청을 처리할 때마다 완전히 새로운 프로세스를 생성해야 하는 오버헤드를 피합니다.
- 이는 요청이 들어올 때 할당되는 프로세스 풀을 미리 생성함으로써 이루어집니다.
- 풀 내의 모든 프로세스가 바쁜 경우에만 들어오는 요청들이 처리 차례를 기다려야 합니다.
- 또한 관리자는 일반적으로 경험하는 부하에 따라 풀 내의 프로세스 수를 조정할 수 있는 능력을 갖게 됩니다.
- 스레드(Threaded)
- 이 유형의 서버는 요청을 처리할 때마다 새로운 스레드를 생성합니다.
- 스레드는 메인 프로세스와 많은 데이터를 공유하므로 새 프로세스를 생성하는 것에 비해 생성 시 약간 더 낮은 오버헤드가 발생합니다.
- 프리스레드(Prethreaded)
- 이것은 프리포크 아키텍처의 스레드 버전입니다.