non-blocking과 async의 차이
async를 구글링하면 non-blocking이 검색결과에 같이 등장한다. 두 가지 모두 서브루틴을 실행할 동안 메인루틴의 동작을 막지않는 방식이라고 막연히 알고 있었는데 사실 async와 non-blocking은 완전히 독립적인 개념이다. 두 개념을 리눅스에서 실행하는 IO 관점에서 알아본다.
blocking & non-blocking
서브루틴에서 IO를 실행할 동안 CPU의 제어가 서브루틴에 있어서 서브루틴이 return할 때 까지 메인루틴이 대기해야 한다면 blocking, 제어가 메인루틴에 있어서 서브루틴의 return을 기다리지 않고 메인루틴의 작업을 진행할 수 있으면 non-blocking이다.
sync & async
메인루틴이 서브루틴을 호출했을 때 호출의 결과를 메인루틴이 처리한다면 sync, 다른 루틴(대표적으로 callback function)이 처리한다면 async 이다.
synchronous & blocking IO
메인루틴은 대기하고 메인루틴이 서브루틴 결과를 처리한다.
위 그림은 리눅스의 synchronous blocking IO 모델이다. 커널 영역에서
read()
작업을 하는 동안, 즉 서브루틴이 실행되는 동안 애플리케이션 프로세스(메인루틴)는 blocking 된다. 커널로부터 응답이 올 때 까지 CPU가 명령을 실행할 수 없으므로 blocking된 프로세스는 실행(running)에서 봉쇄(wait) 상태로 바뀐다. 그럼 OS는 해당 프로세스의 context를 저장하고 CPU가 명령을 실행할 수 있는 준비(ready)상태의 다른 프로세스를 선택해서 CPU를 할당하는 context switch가 발생한다.
read()
가 끝나면 프로세스는 unblock 되고 봉쇄(wait) 상태에서 준비(ready)상태로 바뀐다. synchronous & non-blocking IO
메인루틴은 대기하지 않으나 메인루틴이 서브루틴 결과를 처리한다.
sync & non-blocking 모델은 sync & blocking 에 비해 효율적이지 못하다. 커널의
read()
가 애플리케이션 프로세스를 blocking 하지 않지만 애플리케이션 프로세스는 read()
결과를 처리해야 하므로 커널이 read()
를 완료했는지 주기적으로 확인해야하기 때문이다. 즉, 애플리케이션 프로세스는 polling을 시도한다. 이 경우 CPU는 바쁜대기(busy-wait) 상태이기 때문에 OS는 다른 프로세스에게 CPU를 할당할 수도 없다. 뿐만 아니라 한번의 polling을 하고 나서 다음 polling을 하기 전까지는 이미
read()
가 끝났음에도 커널이 read()
를 완료했는지 알 수 없기 때문에 IO에 지연시간(latency)이 늘어난다.asynchronous & blocking I/O
메인루틴은 대기하지만 서브루틴 결과는 콜백이 처리한다.
커널이
read()
를 끝냈을 때 select()
라는 콜백을 호출하기 때문에 polling은 하지 않지만 애플리케이션 프로세스는 대기(wait) 상태가 된다. (리눅스의 select()
시스템 콜은 IO에 높은 성능을 요구하는 애플리케이션에는 적합하지 않다고 한다.)asynchronous & non-blocking I/O
메인루틴은 대기하지 않고 서브루틴 결과는 콜백이 처리한다.
커널이
read()
를 수행하는 동안 제어는 애플리케이션 프로세스로 넘어간다. 따라서 아주 오래 걸리는 IO가 실행되어도 프로세스는 대기하지 않고 CPU를 사용할 수 있고 IO가 먼저 끝나서 버퍼에 들어온 데이터를 처리할 수도 있다.결론
IO-bound한 애플리케이션을 구현해야하는 경우
- sync & non-blocking IO, async & blocking IO 조합은 선택하지 않는 게 좋다.
- sync & blocking IO 는 위 2가지 조합에 비해 낫지만 짧은 시간의 IO가 자주 있으면 context switching이 빈번하게 발생할 것이다.
- IO 시간이 짧을 경우 async & non-blocking을 권장하지만 IO시간이 길 경우 async & non-blocking 과 sync & blocking 에 큰 차이는 없을 듯.
출처