Blocking과 Non-Blocking, Sync와 Async는 비슷한 부분이 많지만 어떤 것에 관심사를 두고 있는지에 따라 구분되는 개념이기 때문에 헷갈릴 수 있고 구분하기 어려운 개념 중 하나이다.
그래서 각각에 대한 개념과 차이점을 알아보며 정리를 해보겠다.
Blocking과 Non-Blocking의 관심사는 제어권의 반환에 있다.
즉 제어할 수 없는 대상을 어떻게 처리하는가? 에 관한 문제이다.
먼저 그림을 살펴보자
호출된 스레드가 자신의 작업을 모두 마칠 때까지 호출한 스레드에게 제어권을 넘겨주지 않고 대기하게 만드는 것을 말한다.
호출된 스레드가 작업을 마치지 않더라도 호출한 스레드에게 제어권을 넘겨주어 호출한 스레드가 다른 일을 할 수 있도록 하는 것을 말한다.
Synchronous와 Asynchronous의 관심사는 작업 완료 여부를 누가 신경쓰느냐에 있다.
Sync와 Async는 단순히 생각했을 때 Blocking & Non-Blocking과 차이점을 구별하기가 쉽지 않다. 이 개념은 앞선 두 개념에 비해 추상적인 개념이기 때문에 해석의 여지가 생기기 때문이다.
Synchronous의 어원은 Syn(함께) + Chrono(시간), 즉 시간을 함께 맞춘다 라는 뜻이라고 한다.
그럼 어떤 시간을 맞추는 것을 말하는 것인지 알아보자.
Sync는 스레드 A가 스레드 B를 실행시켰을 때 스레드 B의 결과를 스레드 A가 지속적으로 물어보는 것을 말한다.
즉 호출자가 피호출자의 결과를 계속해서 기다린다는 뜻이다.
Sync와는 반대로 Async는 스레드 B의 결과를 스레드 A가 신경쓰지 않기 때문에 스레드 B가 끝마친 결과를 스레드 A에게 가져다고 볼 수 있다.

위의 그림은 Non-Blocking & Sync를 설명한 개념이다.
스레드 A가 스레드 B를 실행시킨 후 제어권을 가져와(Non-Blocking) 자신의 작업을 수행하면서도 스레드 B에게 지속적으로 완료 여부를 요청(Sync)하게 된다.
이때 스레드 B가 만약 작업이 완료되지 않은 상태라면 "완료되지 않음"이라는 결과를 반환하는 것이다.
자바에서는 이 작업을 Future, CompletableFuture, ListenableFuture 을 통해 지원한다.
Future future = asyncFileChannel.read(~~~);
while(!future.isDone()) {
// isDone()은 asyncChannle.read() 작업이 완료되지 않았다면 false를 바로 리턴해준다.
// isDone()은 물어보면 대답을 해줄 뿐 작업 완료를 스스로 신경쓰지 않고,
// isDone()을 호출하는 쪽에서 계속 isDone()을 호출하면서 작업 완료를 신경쓴다.
// asyncChannle.read()이 완료되지 않아도 여기에서 다른 작업 수행 가능
}
// 작업이 완료되면 작업 결과에 따른 다른 작업 처리
위 코드처럼 future.isDone() 메소드를 통해 작업이 끝났는지 여부를 알 수 있다.
스레드 A가 스레드 B를 실행시키고 스레드함수 B의 작업이 끝날 때까지 기다리며(Blocking), 작업이 끝나면 스레드 B가 그 결과를 직접 반환하게 된다.
간혹 Async + Non-Blocking를 잘못 처리하여 위와 같은 방식으로 처리해 성능에 악영향을 줄 수 있다고 한다.
또는 Async + Non-Blocking방식을 사용하는데 그 과정 상 하나라도 Blocking으로 동작하는 부분이 있으면 의도치 않게 Blocking + Async로 동작할 수 있다고 한다.
예시로 Java의 JDBC에서 DB 작업 호출 시 MySQL이 제공하는 드라이버를 사용하는데, 이 드라이버가 Blocking 방식이라고 한다.
하지만 이 상황에서는 스레드 A가 대기를 하고 있기 때문에 사실 Sync + Blocking과 성능적인 차이가 없어 거의 사용하지 않는다.
하지만 이 네가지 개념이 모든 상황에서 뚜렷하게 나눌 수 없다.
다음의 코드를 살펴보자.
factorial(number) 메소드는 멀티 스레드로 비동기/논블로킹 방식으로 작동된다.
futureTask.isDone()메소드 자체도 아주 찰나의 순간이지만 호출하는 순간 잠시 멈추어 다른 스레드의 결과를 받아오게 되므로 동기라 할 수 있다.
System.out.println(...)메소드는 factorial(number)가 실행되는 동안 실행이 되므로 비동기 방식으로 작동한다.
futureTask.get()메소드도 마찬가지로 결과를 받아올 때까지 잠시 멈추어 직접 결과를 받아오므로 동기/블로킹 방식이라 할 수 있다.
하지만 코드 전체적으로 봤을 때 futureTask가 끝난 후 반복문에서 벗어나 futureTask.get()메소드를 호출하기 때문에 이는 동기라 볼 수 있다.
이처럼 내가 바라보는 관점에 따라 하나의 코드 진행에서도 이를 네가지 개념으로 명확히 나누기는 쉽지 않다.
위의 개념들을 명확히 구분하기란 이처럼 쉽지 않다.
하지만 개념적으로 큰 범주로써 나누자면 다음과 같이 정리할 수 있을 것 같다.
Blocking, Non-Blocking: 제어권을 누가 갖는가?
Sync, Async: 호출되는 작업의 완료 여부를 누가 신경쓰느냐?