[프로그래밍] 비동기 프로그래밍

비동기 프로그래밍

요청자(호출자)가 작업을 요청한 후 해당 작업이 완료되었을 때 그 다음 작업을 수행할 수 있다면 요청한 작업은 동기적이라고 한다. 작업들이 정해진 순서에 따라 순차적으로 진행되어야 한다면 각 작업은 동기적으로 호출 및 수행되어야 할 것이다. 앞의 작업이 먼저 수행되어야 뒤의 작업을 수행할 수 있기 때문이다.

반면 요청자가 작업을 요청한 후 해당 작업의 완료 여부에 관계 없이 그 다음 작업을 수행할 수 있다면 요청한 작업은 비동기적이라고 한다. 작업들의 순서가 중요하지 않다면 동기적 호출 방식 대신 비동기 호출 방식을 사용하고 요청 스레드가 차단되지 않도록 함으로써 스레드 자원의 효율성과 애플리케이션의 확장성을 높일 수 있다.

작업의 흐름이 A와 B라고 할 때 다음 시나리오를 살펴보자. 도중에 토스트 요리라는 작업을 예로 들 것이다. 작업은 CPU 연산일 수도 있고 네트워크나 파일 입출력 등과 관련된 I/O 연산일 수 있다.


첫 번째 시나리오: 작업 A의 수행 결과를 작업 B가 사용함

이 경우 작업 A에 대한 요청은 동기적이어야 한다. 작업 A에 대한 수행 결과가 응답으로 반환될 때까지 요청자는 기다릴 수 밖에 없다. 요리사는 토스트기에 빵을 굽고 난 후 구워진 빵에 소스를 바를 것이다.

이때 요청자가 작업 A가 완료될 때까지 아무것도 하지 못한다면 그 원인은 스레드(요청자의 작업 흐름)가 차단(블로킹)되었기 때문이고, 다른 작업도 수행할 수 있다면 그 이유는 스레드가 차단되지 않았기(논블로킹되었기) 때문이다. 스레드의 상태가 조건이 된다. 요리사는 빵이 토스트기에서 구워지는 동안 계란 후라이 요리를 할 수 있을까? 빵을 굽는 과정이 어떤지에 따라 달라진다. 빵을 굽는데 잘 구워지는지 살펴보느라 계란 후라이를 만들지 못할 수도 있다.

작업이 완료되기를 기다리는 동안 아무일도 하지 못한다는 것 자체가 리소스 낭비이므로 동기적 요청과 스레드 블로킹 상황은 리소스 사용 관점에서 비효율적일 수 있다.

요청한 작업이 빠르게 완료되는 것이 보장된다면 큰 문제가 되지 않겠지만 작업이 매우 오래 걸리고 도중 실패할 수도 있다면 기다린 시간이 너무 아까울 것이다. 빵을 굽는 토스트기의 기능이 시원찮다면 요리사는 빵 굽는 작업에 신경이 쓰일 수 밖에 없다.

두 번째 시나리오: 작업 A의 수행 결과를 작업 B가 사용하지 않음

이 경우 작업 A에 대한 요청은 동기적이거나 비동기적일 수 있다. 빵을 다 굽고 나서 계란 후라이 요리를 시작할 필요는 없다.

작업 A가 수행 중일 때 작업 B를 시작 및 수행할 수 있지만(스레드 논블로킹 상황일 것이다) 어떤 이유에서 작업 A가 완료되기만을 기다릴 것이라면 작업 A에 대한 요청은 동기적이어야 한다. 개발자의 의도와 다르게 이러한 상황이라면 동기적 호출을 사용하고 있었을 것이며 비동기 호출을 사용하도록 코드 변경을 할 수도 있다. 의도된 상황이던 그렇지 않던간에 이 경우 동기적 요청 및 스레드 블로킹 상황과 동일하게 리소스 효율면에도 좋지 못하다. 빵이 구워지는 동안 계란 후라이 요리를 해도 되는데 멍을 때리고 하지 않는다던가, 요리사에게 계란 후라이 요리는 빵이 다 구워지면 하라고 비효율적인 지시를 한 상황일 것이다.

작업 A가 완료되지 않고 수행 중이더라도 작업 B를 시작할 것이라면(가능하다는 조건하에) 작업 A에 대한 요청은 비동기적이어야 한다.

작업 A가 요청자의 스레드를 차단하는 블로킹 상황이라면 어떻게 될까. 작업 요청이 비동기적이더라도 작업 B를 수행할 수 없다. 요리사가 빵을 굽는데 잘 구워지는지 살펴보느라 계란 후라이를 만들 수 없는 상황이다. 빵과 계란 후라이만 보면 의존성이 없어보이지만 빵 굽기라는 작업이 요리사의 작업 흐름을 차단시켜 버렸다.

비동기 작업은 콜백(callback) 처리를 수반할 수 있다. 콜백이란 요청한 작업이 완료되었을 때 추가로 실행할 코드를 의미한다. 메인 스레드가 특정 작업 처리를 스레드풀의 작업 스레드에게 할당하는 경우 작업 스레드가 작업을 완료하게 될 때 실행할 코드를 전달할 수 있다. 콜백 메서드는 작업 스레드가 요청한 작업을 완료하면 자동으로 호출된다. 빵이 구워지는 동안 계란 후라이 요리를 비동기적으로 요청하면서 계란 후라이 요리가 완료되면 접시에 옮기라는 작업을 지시할 수 있다. 이 작업이 바로 콜백이다.


스레드 블로킹과 애플리케이션 성능

이와 같이 작업 중에는 요청자의 코드 실행 흐름인 스레드를 차단시키는 작업들이 있다. 예를 들어 데이터베이스 쿼리를 실행하여 결과를 응답으로 반환하는 JDBC는 블로킹 API이다. 이러한 블로킹 작업들은 단일 스레드(또는 제한된 수의 스레드) 환경에서 시스템의 성능을 저하시키거나 아예 불능 상태로 만들어 버리기도 한다. 스레드가 어떤 작업이 끝날 때까지 블로킹되지 않고 다른 작업을 수행할 수 있도록 만든다면 시스템 자원이 허용하는 한도 내에서 스레드 사용 효율을 극대화시킬 수 있다. 이러한 요구사항으로 인해 비동기 및 논블로킹 프로그래밍과 리액티브 프로그래밍 구현 방법이 등장하게 되었다.

스레드는 제한된 자원이다. CPU의 코어 수 보다 많은 수의 스레드를 사용하면 컨텍스트 스위칭으로 인한 오버헤드가 증가하므로 코어 수에 맞게 스레드 풀을 설정하도록 하는 것이 성능 효율 면에서 좋다. 요청해야 할 작업의 수는 많은데 코어와 스레드 수는 제한되어 있기 때문에 스레드를 차단하지 않는 논블로킹 방식으로 동작하도록 애플리케이션을 설계할 필요성이 있는 것이다.

Comments