백엔드 개발자
동기/비동기, 블로킹/논블로킹 본문
동기(Synchronous)
- 작업을 순차적으로 실행한다.
- 호출자가 작업 완료를 기다리며, 작업이 끝난 후에만 다음 작업을 진행한다.
- 코드가 단순하고 직관적이지만 앞 작업이 완료될때까지 기다려야 한다.
비동기(Asynchronous)
- 호출자가 작업을 요청한 후 결과를 기다리지 않고 다른 작업을 수행한다.
- 예: 네트워크 요청에서 비동기 콜백을 설정하여 응답 도착 시 처리.
- 호출자가 대기하지 않으므로 효율적이지만 콜백과 상태 관리로 코드가 복잡해질 수 있다.
동기와 비동기는 작업의 흐름과 실행 순서를 설명하는 개념이다. 동기는 실행 시점이 일치하기 때문에 작업이 끝나는 동시에 다른 작업이 시작된다.
반면 비동기는 실행 시점이 다를 수 있다.
기본적으로 JAVA에서는 동기이다. 예를 들어 어떤 함수를 호출하면 함수로 제어권이 넘어가고, 함수에서 return을 해주면 순차적으로 다음 명령을 실행한다.
블로킹(Blocking)
- 호출자가 작업 완료를 기다리며 실행을 멈춘다.
- 예: InputStream.read() 호출 시 데이터가 도착할 때까지 대기.
- 작업 완료 전까지 다른 일을 수행하지 못한다.
논블로킹(Non-Blocking)
- 호출자가 작업을 대기하지 않고 즉시 제어권을 가져온다.
- 예: SocketChannel의 논블락 모드에서 read()는 데이터가 없으면 바로 반환한다.
- 호출자가 대기하지 않고 다른 작업을 이어갈 수 있지만, 상태 확인이나 콜백 처리가 필요해 코드가 복잡해질 수 있다.
동기/블로킹, 동기/논블로킹, 비동기/블로킹, 비동기/논블로킹
이걸 조합해서 이렇게 네 가지로 나눌 수 있다.
각 방식에 대해 살펴보자.
동기/블로킹
- 자바에서는 기본적으로 동기/블로킹 방식이다. 순차적으로 실행되고, 함수가 호출되었을 때 함수에 제어권이 넘어가고 return 해줄 때 반환된다.(blocking)
```
int result = count();
System.out.println(result);
```
자바 코드를 보면 count() 함수가 호출되고, result에 할당되고 출력하고 있다.
동기/블로킹 방식으로 함수가 일을 수행할 동안 기다렸다가 응답이 오면 순차적으로 실행된다.
동기/논블로킹
- 동기적으로 실행되지만, 호출한 다른 작업을 기다리지 않고 다른 작업을 수행할 수 있다.
- 예를 들어 어떤 일을 수행하기 위해 작업을 실행해놓고, 다른 작업을 이어간다. 여기서 해당 작업이 완료되었는지 계속 확인하면서 완료되었으면 끝나는 즉시 바로 이어서 작업을 수행한다.
- 동기는 순차적으로 실행된다고 했는데 앞 작업이 끝날때까지 기다렸다가 다음 작업을 수행하는 것이다. 이때 기다릴 때 다른 일을 할 수 있는것이 논블라킹 방식이고, 앞 작업이 끝났을 때 바로 실행하는 것(작업 완료 시점과 처리 시점이 일치)이 동기라고 말할 수 있다. 여기서 포인트는 호출자가 제어권을 가지고 계속 작업이 완료되었는지를 확인하고 완료되었을 때 다음 작업을 실행한다.
public class SyncNonBlockingExample {
public static void main(String[] args) {
Task task = new Task();
// 비동기적으로 작업을 실행
new Thread(task::execute).start();
// 다른 작업을 수행하며 상태를 주기적으로 확인
while (!task.isDone()) {
System.out.println("다른 작업 수행 중...");
try {
Thread.sleep(500); // 주기적으로 작업 상태 확인
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 작업이 완료되면 결과 처리
System.out.println("작업 결과: " + task.getResult());
}
}
class Task {
private boolean done = false;
private String result;
public void execute() {
try {
Thread.sleep(2000); // 작업 수행
result = "작업 완료";
done = true;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public boolean isDone() {
return done;
}
public String getResult() {
return result;
}
}
비동기/논블로킹
- 동기/논블로킹과 비슷해서 비교해서 바로 설명하면, 비동기는 순차적으로 실행하지 않아도 된다. 앞 작업이 끝나는 대로 다음 작업을 실행하는 동기와 달리, 종료 시점과 시작시점이 일치하지 않을 수 있다.
- 비동기/논블로킹 방식에서는 어떤 작업을 수행하는 동안 다른 작업을 수행하며, 앞 작업이 끝나면 이벤트 루프나 콜백 함수를 통해 알려준다. 그렇기 때문에 제어권이 호출자가 아닌 작업 시스템(이벤트 루프)로 넘어간다.
비동기/블로킹
- 비동기인데 블로킹은 좀 이상하다. 블로킹할거면 동기로 처리하면 되니까. 이런 경우는 개발자의 실수로 일어난다고 한다. 다들 조심하자~
I/O 작업과 컨텍스트 스위칭
실제 사용되는 예시를 살펴보면 I/O 작업은 일반적으로 동기/블락 형태로 처리된다.
예를 들어, 파일을 읽거나 네트워크 요청을 처리할 때 호출자가 데이터가 준비될 때까지 대기하게 되는데 그래서 I/O 작업이 일어나면 프로세스는 대기 상태가 되고, 컨텍스트 스위칭이 일어나게 된다. (CPU 사용률을 높이기 위해)
그럼 여기서 위에서 말한 4가지 방식을 언제 써야할지 생각해 볼 수 있다. 컨텍스트 스위칭이 많이 일어나면 안좋을텐데, 논블락 방식을 사용하면 안되나?
CPU 바운드와 I/O 바운드 작업에서의 선택 전략
CPU 바운드는 CPU 작업이 많은, CPU가 계속 바쁘게 일해야 하는 작업 유행이고, I/O 바운드는 대부분 네트워크 요청, 파일 읽기/쓰기 등 I/O 작업이 많은 작업을 말한다.
CPU 바운드 작업
- 특징: 연산 작업이 병목(예: 이미지 처리, 암호화).
- 권장 방식:
- 동기/블로킹 방식
- 동기 방식: 작업이 단일 스레드에서 처리되더라도 CPU를 최대로 활용할 수 있다.
- 병렬 처리: 이 작업에서는 논블로킹 방식보다 병렬처리를 고민하는게 더 효과적이다.
- 이유:
- CPU는 계속 바쁘게 동작하므로 대기 상태가 발생하지 않는다.
- 논블로킹 방식은 필요하지 않는다.
I/O 바운드 작업
- 특징: 대기 시간이 병목(예: 네트워크 요청, 파일 읽기).
- 권장 방식:
- 비동기/논블락 방식: 대기 시간을 활용하여 다른 작업을 동시에 처리한다.
- 이유:
- 스레드가 대기 상태에 빠지지 않으므로, 적은 스레드로도 많은 작업을 처리할 수 있다.
- CPU 사용률을 높이고, 컨텍스트 스위칭을 줄일 수 있다.
결론
- 동기/블락 방식은 코드가 간단하지만, I/O 작업에서는 스레드가 유휴 상태로 인해 비효율적일 수 있다.
- 비동기/논블락 방식은 I/O 바운드 작업에서 성능을 극대화할 수 있으며, 컨텍스트 스위칭을 줄여 리소스를 최적화한다.
- 작업의 성격에 따라 적합한 방식을 선택해야 하며, CPU 바운드 작업에서는 병렬 처리를, I/O 바운드 작업에서는 비동기/논블락 방식을 활용하는 것이 효과적이다.
이 주제에 대해 잘 설명해준 영상이 있어 참고했다.
https://www.youtube.com/watch?v=oEIoqGd-Sns
Comments