[Java] 동기와 블로킹, 뭐가 다른가요?

동기와 블로킹은 다른건가요?

 

동기(Synchronous)와 블로킹(Blocking)은 개발을 하다 보면 흔히 접할 수 있는 개념들입니다.

하지만 예전에는 이런 개념들을 막연히 알고 있었고, 그 차이점도 명확하게 이해하지 못했습니다.

특히, '동기'와 '블로킹'이라는 개념을 혼동하며, 아래와 같이 생각했던 적도 있었습니다.

동기(Synchronous)면 블로킹(Blocking) 아닌가?

 

 

동기/비동기, 블로킹/논블로킹은 애플리케이션의 성능과 효율성에 직결되기 때문에, 올바르게 이해하고 활용하기 위해서

2가지 관점에서 자세히 살펴보겠습니다.

 

 

동기, 비동기란?

 

우선 동기,비동기, 블로킹, 논블로킹을 설명할 때 사용되는 단어들의 의미와

동기, 비동기란 무엇인지 간단하게 확인하고 넘어가겠습니다.

 

  • Caller : 작업을 호출하는 함수
  • Callee : 호출된 작업을 수행하는 함수
  • Callback : 비동기 작업이 끝난 후 호출되는 함수

 

 

  • 동기 : Caller는 Callee의 결과에 관심이 있다
  • 비동기 : Caller는 Callee의 결과에 관심이 없다

동기 / 비동기의 구분은 작업을 수행하는 함수인 Callee의 작업 결과를 호출자인 Caller가 돌려받는지의 여부에 따라 결정됩니다.

 

작업을 호출하는 함수인 Caller가 Callee를 호출하면, Callee는 실제 작업을 수행하게 됩니다. 이때, Caller가 Callee의 작업 결과를 돌려받고 이어서 작업을 진행한다면 이를 동기(Synchronous)라고 합니다. 반면, Callee가 작업 결과를 이용해 자체적으로 추가적인 작업(Callback)을 수행하면 이를 비동기(Asynchronous)라고 합니다.

 

 

블로킹, 논블로킹이란?

 

앞서 알아본 동기, 비동기에 이어 블로킹, 논블로킹에 대해서도 간단하게 알아보겠습니다.

 

  • 블로킹 : Caller는 Callee가 완료될때까지 대기한다
  • 논블로킹 : Caller는 Callee를 기다리지 않고 본인의 일을 한다

블로킹/ 논블로킹의 구분은 작업을 수행하는 함수인 Callee가 작업을 수행(execute)하는 동안 호출자인 Caller가 본인의 일을 계속 진행할 수 있는지의 여부에 따라 구분됩니다.

 

작업을 호출하는 함수인 Caller는 작업을 수행하는 함수인 Callee를 호출하면

Callee는 실제 작업을 수행(execute)하게 되는데 이 작업이 끝날 때까지 Caller는 본인의 일을 진행하지 못하고 대기하게 되는데 이것을 블로킹(Blocking)이라고 합니다. 반면, Callee가 실제 작업을 수행하는 동안 Caller가 대기하지 않고 본인의 일을 진행한다면 이것을 논블로킹(Non-Blocking)이라고 합니다.

 

 

 

함수 호출 관점

 

아래의 4가지 그림들은 Caller와 Callee가 함수를 호출하는 과정을 시간선에 따라 도식화한 것입니다.

Blocking이라고 적힌 구간은 본인의 일을 진행하지 못하고 다른 작업을 기다리고 있다는 의미이며,

빗금 친 구간은 함수가 본인의 일을 진행하고 있다는 의미입니다.

 

동기 (블로킹)

  • Caller는 Callee가 return해준 결과를 이용해서 작업을 수행한다.
  • 제어권을 Callee가 갖고 있다.
  • 별도의 thread를 필요로 하지 않는다.

 

 

 

 

 

비동기 (블로킹)

  • Callee는 실행 결과를 이용해서 callback을 수행한다.
  • 제어권을 Callee가 갖고 있다.
  • 별도의 thread를 필요로 하지 않는다.

 

 

 

 

 

동기 (논블로킹)

  • Caller는 본인의 일을 하다가 Callee가 return해준 결과를 이용해서 작업을 수행한다.
  • 제어권을 Caller가 갖고 있다.
  • Caller와 다른 별도의 thread가 필요하다.

 

 

 

 

 

 

 

비동기 (논블로킹)

  • Callee는 실행 결과를 이용해서 callback을 수행한다.
  • 제어권을 Caller가 갖고 있다.
  • Caller와 다른 별도의 thread가 필요하다.

 

 

 

 

 

 

 

 

블로킹의 종류

 

위에서 블로킹에 대해 간략히 살펴보았는데, 블로킹은 두 가지 유형으로 나눌 수 있습니다.

 

첫 번째는 연산을 계속 진행하며 CPU를 점유하는 CPU-bound blocking입니다. 이는 함수 호출 관점에서 발생하는 블로킹으로, 작업이 CPU 자원을 지속적으로 사용하며 다른 작업을 대기시키는 상황입니다. 

 

두 번째는 다른 작업을 대기하며 발생하는 I/O-bound blocking입니다. 이는 파일 읽기/쓰기와 같은 I/O 작업에서 발생하며, CPU는 대기 상태에 있고 다른 작업이 완료되기를 기다리는 상황입니다.

▷ CPU-bound blocking

  • 연산이 많은 경우 thread가 CPU를 계속 점유하며 블로킹
  • 추가적인 코어를 투입하여 해결 가능

▷ IO-bound blocking

  • 파일 읽기/쓰기, network 요청 처리, 요청 전달 등 thread가 대기하며 블로킹
  • I/O Non-Blocking으로 구현하여 해결 가능

 

이처럼 블로킹은 CPU 자원을 사용하는 정도에 따라 다르게 분류되며, 각 경우에 따라 최적화 방법이 달라질 수 있습니다.

 

 

 

I/O 관점

 

아래의 3가지 그림들은 Application이 Kernel에 system call을 요청하는 과정을 시간선에 따라 도식화한 것입니다.

Blocking이라고 적힌 구간은 본인의 일을 진행하지 못하고 다른 작업을 기다리고 있다는 의미이며,

빗금 친 구간은 본인의 일을 진행하고 있다는 의미입니다.

 

동기 (블로킹)

    • Application은 Kernel이 return해준 결과를 이용해서 작업을 수행한다.
    • recvfrom을 호출 후 blocking socket을 이용하여 read/write를 수행한다.

 

 

 

 

 

 

* Kernel : 하드웨어와 애플리케이션 사이에서 중개자 역할을 수행하는 운영체제의 소프트웨어
* System Call : 운영체제의 Kernel을 호출하는 인터페이스
* Recvfrom : 네트워크 소켓을 통해 데이터를 수신하는 함수 (System Call을 사용하는 함수)

 

비동기 (블로킹) 의 경우 Multiplexing I/O 가 해당된다고 말하기도 하지만,
결국 내부적으로 동기 (논블로킹)으로 동작하기 때문에 생략하였습니다.

 

 

동기 (논블로킹)

    • Application은 본인의 일을 하다가 Kernel이 return해준 결과를 이용해서 작업을 수행한다.
    • recvfrom을 호출 후 non-blocking socket을 이용하여 read/write를 수행한다.
    • recefrom을 주기적으로 호출하여 작업이 완료되었는지 확인하며, 작업이 완료되지 않았다면 EAGAIN/EWOULDBLOCK 에러를 반환한다.

 

 

 

 

비동기 (논블로킹)

    • Application의 thread2는 Kernel이 return해준 결과를 이용해서 callback을 수행한다.
    • aio_read를 호출하여 작업이 완료되면 Kernel이 완료 시그널을 보내거나 callback을 호출한다.

 

 

 

 

 

 

 

마치며

 

이번 글에서는 동기, 비동기, 블로킹, 논블로킹에 대해 두 가지 관점에서 공부해 보았습니다.

각 방식이 가진 특징과 장단점들이 있기 때문에, 상황에 맞게 적절한 방식을 선택하는 것이 중요하다고 생각합니다.

다음 글에서는 Java에서 비동기 작업을 처리하기 위한 클래스를 살펴보겠습니다.

'Java' 카테고리의 다른 글

[Java] I/O 작업도 비동기로 가능한가요?  (1) 2025.02.12
[Java] Multiplexer가 뭔가요?  (0) 2025.02.11
[Java] CompletableFuture 란?  (0) 2025.02.11