728x90
동기 프로그래밍
- 작업의 실행 흐름이 순차적으로 동작한다.
- 순차적으로 동작하는 프로그램은 코드를 파악하기 쉽고, 결과를 예측하기 쉬우므로 디버깅이 쉽다.
- 특정 작업을 실행하는 동안 다른 작업을 할 수 없다는 단점이 존재한다.
비동기 프로그래밍
- 작업의 실행흐름이 기본적으로 순차적이지 않다.
- 비동기 처리 방식은 현재 실행 중인 작업이 끝나는 것을 기다리지 않고 다른 작업을 할 수 있다.
- 서버, 클라이언트 등 모든 환경에서 유용하게 사용된다.
- UI 애플리케이션의 경우 특정 이벤트가 발생할 경우에 반응하는 동작을 구현해야 하는데, 이럴 때 필수적으로 비동기 프로그래밍을 사용하게 된다.
- 대부분 프로그래밍 언어들은 각 언어의 철학에 맞는 다양한 비동기 처리 방법을 지원한다.
- 대표적으로 callback, promise, future, async-await, coroutine 등이 있다.
비동기 프로그래밍 구현
Thread
fun main() {
for (i in 0..5) {
val thread = Thread {
println("current-thread-name: ${Thread.currentThread().name}")
}
thread.start()
}
println("current-thread-name: ${Thread.currentThread().name}") //main 스레드 출력 -> 프로그래밍 실행될 때 가장 기본으로 실행되는 스레드
}
- 가장 기본이 되는 비동기 처리 방식이다.
- 스레드는 Runnable 인터페이스를 사용해 비동기 동작을 수행한다.
- 스레드가 1개인 경우 싱글 스레드라고 부르고, 하나 이상 존재하는 경우 멀티스레드라고 한다.
- 멀티 스레드를 사용하면 애플리케이션에서 여러개의 작업을 동시에 할 수 있다.
- 멀티 스레드에서, 스케쥴링 알고리즘에 의해 스레드가 전환되며, 작업을 처리하는데 이를 컨텍스트 스위칭이라 한다.
- 하나의 프로세스에는 최소 하나 이상의 스레드가 존재, 프로세스 내의 스레드들은 동일한 메모리를 공유한다.
- 스레드는 프로세스를 생성하는것 보다 가볍다.
ExecutorSerivce
fun main() {
val pool = Executors.newFixedThreadPool(5) //Executor Service에서 스레드를 꺼내오는 펙토리 메서드, 파라미터 값은 스레드의 유지 갯수
try {
for (i in 0..5) {
pool.execute {
println("current-thread-name : ${Thread.currentThread().name}")
}
}
}finally {
pool.shutdown()
}
println("current-thread-name : ${Thread.currentThread().name}")
}
- 스레드가 무한정 많아지면 OOM 이 발생할 수 있고, 높은 처리량을 요구하는 시스템에서는 스레드를 생성하는데 대기하는 시간 때문에 응답 지연이 발생할 수 있다
- 이를 해결하기 위해 스레드 풀을 사용하며, 애플리케이션 내의 스레드의 총개수를 제한할 수 있고, 기존에 생성된 스레드를 재사용하여 빠른 응답이 가능하다.
- 검증된 라이브러리를 사용해 작성된 스레드 풀을 사용해야하며, java.util.concurrent 패키지의 ExecutorService를 사용하면 쉽고 안전하게 스레드 풀을 사용할 수 있다.
- 출력 결과를 보면 스레드 풀에서 관리하는 스레드임을 알 수 있고, 동일한 스레드 이름을 보고 스레드 풀에 있는 스레드를 재사용한 것을 파악할 수 있다.
Future
fun sum(a: Int, b: Int) = a + b
fun main() {
val pool = Executors.newSingleThreadExecutor()
val future = pool.submit(Callable {
sum(100, 200)
})
println("계산 시작")
val futureResult = future.get() //비동기 작업을 결과를 기다린다.
// get 함수를 사용하게 되면 스레드가 작업을 완료할 때까지 스레드가 블록킹 된다
println(futureResult)
println("계산 종료")
}
- future는 비동기 작업에 대한 결과를 얻고 싶은 경우에 사용된다.
- 수행 시간이 오래 걸리는 작업(db접근, API 호출)에 대 한 결과를 기다리면서 다른 작업을 병행해서 수행하고 싶은 경우 유용
- 스레드는 Runnable을 사용해 비동기 처리를 하지만, future를 사용해 처리 결과를 얻기 위해선 callable을 사용한다.
- future를 사용하면 비동기 작업을 쉽게 구현할 수 있지만, 단점도 있다.
- get 함수는 비동기 작업의 처리가 완료될 때까지 다음 코드로 넘어가지 않고 무한정 대기/ 지정해둔 타임 아웃까지 블로킹된다.
- future를 사용하면 동시에 실행되는 한 개 이상의 비동기 작업에 대한 결과를 하나로 조합하여 처리하거나, 수동으로 완료처리할 수 있는 방법을 지원하지 않는다.
Completable Future
//CompletableFuture를 사용해서 자바에서 비동기 논 블로킹 작업을 쉽게 구현할 수 있다.
fun main() {
val completableFuture = CompletableFuture.supplyAsync {
Thread.sleep(2000)
sum(100, 200)
}
println("계산 시작")
completableFuture.thenApplyAsync(::println) //논 블로킹으로 동작
/*
val result = completableFuture.get() //블로킹 동작
println(result)
*/
while (!completableFuture.isDone) { //종료 될때까지 대기
//completableFuture.isCancelled //취소 여부
//completableFuture.isCompletedExceptionally //오류 발생 여부
Thread.sleep(500)
println("계산 결과를 집계 중입니다.")
}
println("계산 종료")
}
- JDK8 이상부터 future을 단점을 극복하기 위해 CompletableFuture를 제공한다.
- 펙토리 함수인 supplyAsync 함수를 사용해 비동기 작업을 수행할 수 있다.
- thenApplyAsync 함수를 사용해 논블로킹으로 동작하고 뒤에 Async가 붙은 함수들은 supplyAsync와 별도의 스레드 풀을 지정할 수 있다
- isDone 으로 CompletableFuture가 수행 중인 비동기 작업이 완료된 상태인지를 알 수 있다
- 취소 상태를 나타내는 isCancelled, 비동기 작업 도중에 에러가 발생한 상태를 나타 내는 isCompletedExceptionally 도 제공한다.
- CompletableFuture를 사용하더라도 get 함수를 그대로 사용하면 코드가 블록킹 된다
728x90
'실무 프로젝트로 배우는 Kotlin & Spring > 리액티브 프로그래밍' 카테고리의 다른 글
| 리액티브 프로그래밍 (0) | 2022.12.01 |
|---|---|
| Iterator 패턴 (0) | 2022.12.01 |
| Observer 패턴 (0) | 2022.11.28 |