Coroutine
먼저 알아야 할 것들
- 서브루틴subroutine
-
-
여러 명령어를 모아 이름을 부여해서 반복 호출을 할 수 있게 정의한 프로그램 구성 요소(a.k.a. 함수).
-
객체지향 언어에서는 메서드도 서브루틴이라 할 수 있음.
-
서브루틴에 진입하는 방법은 오직 한 가지 뿐.
-
해당 함수를 호출하면 서브루틴의 맨 처음부터 실행 시작.
-
시작될 때 마다 활성 레코드activation record가 스택에 할당되면서 서브루틴 내부의 로컬 변수 등이 초기화 됨.
-
-
서브루틴 안에서 여러 번 return 을 사용할 수 있음.
-
서브루틴이 실행을 중단하고 제어를 호출한쪽caller에게 돌려주는 지점은 여럴 있을 수 있음.
-
다만 서브루틴에서 반환되고 나면 활성 레코드가 스택에서 사라지기 때문에 실행 중이던 모든 상태를 잃어버림.
-
서브루틴을 여러 번 반복 실행해도 항상 같은 결과를 얻게 됨(side-effect가 있지 않는 한)
-
-
-
- 멀티태스킹multitasking
-
-
여러 작업을 동시에 수행하는 것처럼 보이거나, 실제로 동시에 수행하는 것.
-
- 비선점형non-preemptive
-
-
멀티태스킹의 각 작업을 실행하는 참여자들의 실행을 운영체제가 강제로 일시 중단시키고 다른 참여자를 실행하게 만들 수 없다는 뜻.
-
각 참여자들이 서로 자발적으로 협력해야만 비선점형 멀티태스킹이 제대로 작동할 수 있음.
-
Coroutine?
코루틴(coroutine)은 루틴의 일종으로서, 협동 루틴이라 할 수 있다(코루틴의 "Co"는 with 또는 togather를 뜻한다). 상호 연계 프로그램을 일컫는다고도 표현가능하다. 루틴과 서브 루틴은 서로 비대칭적인 관계이지만, 코루틴들은 완전히 대칭적인, 즉 서로가 서로를 호출하는 관계이다. 코루틴들에서는 무엇이 무엇의 서브루틴인지를 구분하는 것이 불가능하다. 코루틴 A와 B가 있다고 할 때, A를 프로그래밍 할 때는 B를 A의 서브루틴으로 생각한다. 그러나 B를 프로그래밍할 때는 A가 B의 서브루틴이라고 생각한다. 어떠한 코루틴이 발동될 때 마다 해당 코루틴은 이전에 자신의 실행이 마지막으로 중단되었던 지점 다음의 장소에서 실행을 재개한다.
코루틴
코루틴은 컴퓨터 프로그램 구성 요소 중 하나로 비선점형 멀티태스킹(non-preemptive multitasking)을 수행하는 일반화한 서브루틴(subroutine)이다. 코루틴은 실행을 일시 중단(suspend)하고 재개(resume)할 수 있는 여러 진입 지점(entry point)을 허용한다.
-
코틀린kotlin과 이름이 비슷해서 코틀린 기능이라고 생각할 수 있지만, 여러 언어에서 지원하는 개념.
-
concurrency design pattern.
-
코틀린 팀은 코루틴을
경량 스레드: Light-weighted thread
로 정의.-
한 thread에서 다수의 coroutine을 수행할 수 있음과 Context Switching이 필요없기 때문에 이렇게 부름
-
-
Thread 보다 리소스를 더 효율적으로 사용하면서 더 쉽게 작동.
-
Thread는 아니지만 비동기적인asynchronous 프로그래밍이 가능하게 만들어줌.
-
Thread는 '중단block'되지만, Coroutine은 '보류suspend' 됨.
-
Thread보다 더 좋은 성능을 제공.
-
Thread는 중단될 때 중단이 풀릴 때까지 아무 일도 할 수 없음.
-
코루틴은 Thread에 의해 실행되며, 코루틴을 실행하는 스레드를 중단시키지 않음.
-
대신에 보류된 함수를 실행하는 Thread는 다른 Coroutine을 실행하는 데 사용될 수 있음.
-
내부적으로 실행이 보류되는 함수를
suspend
키워드로 나타냄.
-
@startuml [Component] --> Interface1 [Component] -> Interface2 @enduml
Thread vs Coroutine
Kotlin의 Coroutine
-
Kotlin 1.1부터 코루틴 API 제공
-
Kotlin 1.3부터 표준 라이러리에 정식 포함
-
코루틴을 사용하려면 코루틴 확장 라이브러리가 필요하다.
-
코루틴 라이브러리에서 제공하는
async
함수를 사용하면 코루팀을 생성할 수 있다.
Suspending functions
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
fun fetchCharacterData(): Deferred<CharacterGenerator.CharacterData> { (1)
return GlobalScope.async { (2)
val apiData = URL(API_URL).readText()
CharacterGenerator.fromApiData(apiData)
}
}
1 | Deferred 는 우리가 요청할 때까지 데이터를 반환하지 않는다. |
2 | async 는 하나의 인자로 람다를 받으며, 람다에 백그라운드에서 처리할 작업을 지정한다. |
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class AppService {
fun onCreate() {
GlobalScope.launch(Dispatchers.Main) { (1) (2)
characterData = fetchCharacterData().await() (4)
displayData() (3)
}
}
}
1 | launch 함수는 코루팀을 생성하며, launch 함수는 블록안에 지정한 람다(코루틴 코드)를 시작시킨다. |
2 | launch 함수의 파라미터에는 해당 작업이 실행되는 스레드를 나타낸다. Dispatcher.Main 은 안드로이드의 UI 스레드이다. |
3 | 이 코드를 안드로이드로 예를 들었을 때, displayData() 함수는 UI를 변경시키는 작업이므로, UI 스레드를 지정시켰다. |
4 | 코루틴 컨텍스트의 기본 인자는 CommonPool 이다. 이것은 코루틴이 실행될 때 사용될 수 있는 백그라운드 스레드 풀이다.따라서 await 를 호출할 때 해당 작업은 CommonPool의 스레드 중 하나를 사용한다. |
launch vs async/await
-
async
,launch
함수를 coroutine builder function 이라고 한다.-
이 함수들은 특정 방법으로 작업을 수행하도록 코루틴을 설정한다.
-
-
launch
는 우리가 지정한 작업을 올바르게 수행하는 코루틴을 빌드한다. -
async
는 지연된(아직 완료되지 않은) 작업을 나타내는Deferred
를 반환하는 코루팀을 빌드한다.-
즉, 해당 작업이 바로 시작되어 끝나는 것이 아니다.
-
-
Deferred
타입은await
함수를 제공한다.-
await
함수는 우리가 원하는 작업 수행 시점에 호출한다. -
await
함수는 지연된 작업이 완료될 때까지 다음에 할 작업을 보류한다.
-
-
Deferred
는 Java의Future
와 유사한 방법으로 동작한다.
yield
reactive vs coroutine
Fiber
WebClient with Coroutines
suspending extension 함수인 awaitBody()
를 활용할 수 있다.
val htmlResponse = webClient.get()
.uri("https://www.baeldung.com/")
.retrieve()
.awaitBody<String>()
retrieve()
함수는 API 요청의 응답 코드가 2xx일 경우에만 반환하고, 나머지는 예외를 던진다. 다양한 응답 코드에 대한
핸들링이 필요하다면 awaitExchange()
확장 함수를 활용할 수 있다.
val response: ResponseEntity<String> = webClient.get()
.uri("https://www.baeldung.com/")
.awaitExchange()
.awaitEntity()
위와 같은 코드에서는 API 응답이 ResponseEntity
로 반환되므로, 상태 코드에 따른 처리가 가능해진다.
@GetMapping("/payments/{id}/")
suspend fun fundPayment(@PathVariable id: String): PaymentView {
val
return PaymentView()
}