입출력을 기다리는 두 가지 방식, 멈추고 기다리기(Blocking I/O)와 멈추지 않고 기다리기(Non-Blocking I/O)
멈추고 기다리기(Blocking I/O)
- 프로그램이 외부와 데이터를 주고 받을 때, 외부에서 데이터를 처리하는 동안 기다리는 것
- 외부에서 작업을 진행하는 동안 프로그램의 자원은 외부 작업이 끝나기를 기다리는 데에 소비한다.
멈추지 않고 기다리기(Non-Blocking I/O)
- 외부에서 필요한 작업을 하는 동안 프로그램은 다른 일을 할 수 있다.
- 프로그램의 자원을 훨씬 효율적으로 사용할 수 있다.
- 자신의 현재 맥락과 상관없이 외부 작업과 데이터를 공유할 방법이 필요하고, 이를 위한 추가 구현이 필요하다.
결과를 기다리는 방식에 따라 다시 분류되는 멈추지 않고 기다리기
- 주도적으로 데이터를 가져오는 방식(Pull Base)을 사용하는 경우를 멈추지 않고 기다리기(Non-Blocking I/O)
- 수동적으로 받아오는 방식(Push Base)을 사용하는 경우를 비동기 방식으로 입출력 기다리기(Asynchronous I/O)
- 일반적으로 수동적으로 받아오는 방식이 자원을 훨씬 효율적으로 사용할 수 있지만, 주도적으로 가져오는 방식에 비해 구현해야 하는 코드가 많고, 복잡하다.
여러가지 일을 다루는 두 가지 방식, 병렬성(Parallelism)과 동시성(Concurrency)
병렬성
- 작업을 처리하는 일꾼을 여러명 두어 같은 작업을 동시에 수행
- 일꾼이 여러명이어야지 성립하는 개념이다.
- 일꾼을 코어라고 생각하면, 두개 이상의 코어가 있어야 성립된다.
동시성
- 일꾼이 여러가지 일을 동시에 수행
- 일꾼의 수와는 상관없이 일꾼이 일하는 방식과 연관이 있다.
- 일꾼이 하는 일들을 스레드라고 생각하면, 멀티스레드 방식인 경우 성립된다.
결론
- 동시성은 자원을 효율적으로 사용하는 것이 목적이고, 병렬성은 많은 자원을 투자해서 일의 처리량을 늘리는 것이 목적이다.
- 동시성과 멈추지 않고 기다리기를 같이 사용하면, 시너지 효과를 낼 수 있지만, 둘 사이에 의존성은 전혀 없다.
- 동시성을 확보한 프로그램이 멈추지 않고 기다리기를 사용할 수도 있고, 사용하지 않을 수도 있다.
비동기 프로그래밍(Asynchronous Programming)
- 실행한 코드의 결과를 기다리지 않고 별도의 채널에 결과 처리를 맡긴 후 다음 작업을 바로 진행하는 방식의 프로그래밍
- 동시성(Concurrency), 멈추지 않고 기다리기(Non-Blocking I/O)와
매우 흡사한 개념 같아 보이지만 독립된 정의로 다루어야 한다.
‘결과를 기다리지 않고 다음 코드를 실행하는 흐름’ 을 대표하는 개념이다. 이를 구현하기 위해 여러 일꾼을 사용하여 실행하도록 하든 일꾼 하나에 작업의 순서를 조절하여 실행하도록 하든, 어떻게 구현할 지는 비동기 프로그래밍과 연관이 있을 지언정 정의의 구성 요소라고 볼 수는 없다.
🙋♀️이벤트 루프(Event Loop)를 사용하여 병렬성을 확보하지 않고 오직 동시성만
으로 비동기 프로그래밍을 제공하는 방식이 있다.
🙋♀️함수를 전달하여 처리하기(Callback) 방식을 사용하여 동시성 없이 비동기 프로그래밍을 제공할 수도 있다.
스레드 풀(Thread Pool)과 비동기 프로그래밍(Asynchronous Programming)
스레드 풀
- 일의 단위를 스레드로 두고, 미리 만들어둔 스레드를 풀에서 필요할 때 하나씩 꺼내어 사용하고 일이 끝나면 다시 풀에 넣어 다른 일에 사용할 수 있는 상태로 두는 방식이다.
- 병렬성과 동시성을 확보하여 작업을 효율적으로 수행하기 위한 기법이다.
1. 스레드 1이 일을 진행한다.
2. 비동기를 실행해야 할 코드를 만나면, 스레드 풀에서 스레드 2를 꺼내어 비동기로 진행할 일을 맡긴다. 스레드 1은 비동기 실행에 관여하지 않고, 나머지 일을 마저 진행한다.
3. 스레드 2는 일이 끝나면 약속에 맞게, Callback / Future, Promise 로 처리하고 다시 스레드 풀로 돌아간다.
스레드 풀을 구현체로 사용하는 비동기 프로그래밍을 사용 할 때 고려해야 할 사항
- 스레드 풀을 구현체로 사용하는 비동기 프로그래밍을 사용 할 때에는 오래 걸리는 작업으로 인해 스레드가 고갈되지 않도록 신경 써야 한다.
- 이 문제를 해결하기 위해 오래 걸리는 작업만을 처리하기 위한 별도의 스레드 풀을
사용하는 방식을 사용한다
- 비동기 작업 내부에서 일어나는 입출력을 되도록 멈추지 않고 기다리는 방식(Non-Blocking I/O)으로 처리해야 하는 점이다.
- 스레드 풀을 사용하는 방식으로 비동기 프로그래밍을 지원하고 있다면 오래 걸리는 입출력 작업에 대해 되도록 멈추지 않고 기다리는 방식으로 처리하도록 해야한다.
references