모든 내용은 Do it! 코틀린 프로그래밍을 바탕으로 정리한 것입니다.
프로세스나 스레드는 해당 작업을 중단(stopped)하고 다른 루틴을 실행하기 위한 문맥 교환을 시도할 때 많은 비용이 듦.
코루틴(Coroutine)은 비용이 많이 드는 문맥 교환없이 루틴을 일시 중단(suspended)하여 비용을 줄일 수 있음.
→ 운영체제가 스케줄링에 개입하는 과정이 필요하지 않다는 것 & 일시 중단은 사용자가 제어할 수 있음
< 코루틴의 주요 패키지; kotlinx.coroutines.* >
- common 패키지
- launch / async : 코루틴 빌더
- Job / Deferred : cancellation 지원
- Dispatchers : Default는 백그라운드 코루틴을 위한 것이고 Main은 Android, Swing, JavaFx를 위해 사용됨
- delay / yield : 상위 레벨 지연(suspending) 함수
- Channel / Mutex : 통신과 동기화를 위한 기능
- coroutineScope / supervisorScope : 범위 빌더
- select : 표현식 지원
- core 패키지
- CommonPool : 코루틴 문맥
- produce / actor : 코루틴 빌더
launch와 async
fun main() {
// 메인 스레드
GlobalScope.launch { // 새로운 코루틴을 백그라운드에 실행
delay(1000L) // 넌블로킹 1초 지연
println("World!") // 지연 후 출력
}
println("Hello,") // 메인 스레드의 코루틴이 지연되는 동안 실행
Thread.sleep(2000L) // 메인 스레드가 JVM에서 바로 종료되지 않게 2초 기다림
}
- 메인 스레드 - main() 함수 블록
- 코루틴 코드는 메인 스레드와 별도로 실행되는 넌블로킹 코드
코루틴에서 사용되는 함수는 suspend()로 선언된 지연함수여야 코루틴 기능을 사용할 수 있음
suspend 키워드를 사용함으로서 이 함수는 실행이 일시 중단(suspended)될 수 있으며 필요에 따라 다시 재개(resume)할 수 있음
public suspend fun delay(timeMillis: kotlin.Long): kotlin.Unit { /* compiled code */ }
👆 suspend 함수는 코루틴 블록 안에서만 사용가능함
(코루틴 빌더인 launch와 async나 또 다른 지연함수에서만 사용할 수 있음)
→ 컴파일러가 suspend가 붙은 함수를 자동적으로 추출해 Continuation 클래스로부터 분리된 루틴을 만듦
launch 코루틴 빌더 생성
launch를 통해 코루틴 블록을 만들어내는 것
import kotlinx.coroutines.*
fun main() {
val job = GlobalScope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
println("job.isActive: ${job.isActive}, complete: ${job.isCompleted}")
Thread.sleep(2000L)
println("job.isActive: ${job.isActive}, complete: ${job.isCompleted}")
}
// Hello,
// job.isActive: true, complete: false
// World!
// job.isActive: false, complete: true
- launch는 현재 스레드를 차단하지 않고 새로운 코루틴을 실행할 수 있게함
- 특정 결과값 없이 Job객체를 반환함
- GlobalScope는 생명주기가 프로그램의 생명 주기에 의존되므로 main()이 종료되면 같이 종료됨
내부적으로 비동기 코드로 동시에 작동할 수 있지만
코드만 봤을 때는 순차적으로 실행되는 것처럼 표현하여 프로그래밍의 복잡도를 낮출 수 있음
😎 Job 객체란?
Job은 백그라운드에서 실행하는 작업을 가리킴
간단한 생명주기를 가지고 있고 부모-자식 관계가 형성되면 부모의 작업이 취소될 때 하위 자식의 작업이 모두 취소됨
보통 Job() 팩토리 함수나 launch에 의해 job 객체가 생성됨
상태 | isActive | isCompleted | isCancelled |
New | false | false | false |
Active | true | false | false |
Completing | true | false | false |
Cancelling | false | false | true |
Cancelled (최종 상태) | false | true | true |
Completed (최종 상태) | false | true | false |
- 보통 Job이 생성되면 활성화된 상태인 Active
- Job() 팩토리 함수의 인자로 CoroutineStart.Lazy(자세한 설명은 아래에)를 설정하면 활성화되지 않고 New
- Active 상태로 만들기 위해선 start()나 join()
- cancel()을 이용하면 Cancelling 상태로 즉시 바뀌고 이후 Cancelled 상태로 바뀜
async 코루틴 빌더 생성
launch와 다른 점은 Deffered<T>를 통해 결과값을 반환하는 것
private fun worksInParallel() {
val one = GlobalScope.async {
doWork1()
}
val two = GlobalScope.async {
doWork2()
}
GlobalScope.launch {
val combined = one.await() + "_" + two.await()
println("Kotlin Combined : $combined")
}
}
- doWork1()과 doWork2()는 async에 의해 완전히 병행 수행
- await() : 지연된 결과값을 받기 위해 사용할 수 있음
- 태스크가 종료되는 시점을 기다렸다가 결과를 받을 수 있음
- 현재 스레드의 블로킹없이 먼저 종료되면 결과를 가져올 수 있음
안드로이드 UI 스레드에서 블로킹 가능성이 있는 코드를 사용하면 앱이 중단되거나 멈추는 경우가 있는데,
await()을 사용하면 UI를 제외한 루틴만 블로킹되므로 UI가 멈추는 경우를 해결할 수 있음
코루틴 문맥; CoroutineContext
코루틴이 실행될 때 여러 가지 문맥은 CoroutineContext에 의해 정의됨
내부적으로 CommonPool이 지정되어 코루틴이 사용할 스레드의 공동 풀을 사용함
→ 이미 초기화되어 있는 스레드 중 하나 혹은 그 이상이 선택되며 초기화하기 때문에 스레드를 생성하는 오버헤드가 없는 빠른 기법
*오버헤드(overhead) : 어떤 처리를 하기 위해 들어가는 간접적인 처리 시간, 메모리 등
→ 하나의 스레드에 다수의 코루틴이 동작할 수 있음
시작 시점에 대한 속성
필요한 경우 launch()나 async()에 인자를 지정해 코루틴에 필요한 속성을 줄 수 있음
public fun kotlinx.coroutines.CoroutineScope.launch(
context: kotlin.coroutines.CoroutineContext /* = compiled code */,
start: kotlinx.coroutines.CoroutineStart /* = compiled code */,
block: suspend kotlinx.coroutines.CoroutineScope.() -> kotlin.Unit): kotlinx.coroutines.Job { /* compiled code */ }
context 매개변수 이외에도 start 매개변수를 지정할 수 있음
- DEFAULT : 즉시 시작
- LAZY : 코루틴을 느리게 시작(처음에는 중단된 상태이며 start()나 awit() 등으로 시작)
- ATOMIC : 최적화된 방법으로 시작
- UNDISPATCHED : 분산 처리 방법으로 시작
public fun <T> kotlinx.coroutines.CoroutineScope.async(
context: kotlin.coroutines.CoroutineContext /* = compiled code */,
start: kotlinx.coroutines.CoroutineStart /* = compiled code */,
block: suspend kotlinx.coroutines.CoroutineScope.() -> T): kotlinx.coroutines.Deferred<T> { /* compiled code */ }
async() 함수도 start 매개변수를 지정하여 시작 시점을 조절할 수 있음
CoroutineStart.Lazy를 사용하면 코루틴 함수를 호출하거나 await() 함수를 호출하는 시점에 async()가 실행되도록 할 수 있음
import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis
suspend fun doWork1(): String {
delay(1000)
return "Work1"
}
suspend fun doWork2(): String {
delay(3000)
return "Work2"
}
fun main() = runBlocking {
val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY ) { doWork1() }
val two = async(start = CoroutineStart.LAZY) { doWork2() }
println("AWAIT: ${one.await() + "_" + two.await()}")
}
println("Completed in $time ms")
}
runBlocking
새로운 코루틴을 실행하고 완료되기 전까지 현재 스레드를 블로킹함
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
- 메인 스레드 자체를 잡아두기 위해 main() 함수 자체를 블로킹 모드에서 실행할 수 있음
- main() 내부의 코루틴이 모두 작동할 때까지 delay() 함수를 사용하여 기다리지 않아도 자동적으로 블로킹
- 자료형 <Unit>은 생략할 수 있음
코틀린 1.3 버전부터 main() 함수에도 suspend 키워드를 지정할 수 있음
suspend fun main() = coroutineScope {
클래스 내의 멤버 메소드에서도 사용할 수 있음
class Block {
...
fun mySuspendMethod() = runBlocking {
...
}
}
join() 함수
명시적으로 코루틴 작업이 완료되는 것을 기다리게 하려면 Job객체의 join() 함수를 사용함
launch에서 Job 객체를 반환하기 때문에 이것을 이용하여 main() 함수에서 join()을 호출할 수 있음
fun main() = runBlocking<Unit> {
val job = launch {
delay(1000L)
println("World!")
}
println("Hello")
job.join()
}
위의 코드에서 job객체를 이용하여 join()을 명시하지 않아도 runBlocking에 의해 main()은 코루틴이 끝나기 전까지 블로킹됨
하지만 job.cancel()로 작업을 취소한다면 코루틴 작업이 취소되는 것이기 때문에 결과는 Hello만 출력됨
*블로킹과 넌블로킹에 대한 설명은 아래 포스팅 참고!
'Kotlin > Kotlin 프로그래밍' 카테고리의 다른 글
[Kotlin] Kotlin의 싱글톤 패턴(Singleton Pattern) (2) | 2022.02.05 |
---|---|
[Kotlin] 클로저와 표준 함수 let, also, apply, run, with (0) | 2021.07.08 |
[Kotlin/Coroutine] 코루틴(Coroutine) - 동시성 프로그래밍 (0) | 2021.06.03 |
[Kotlin] 코틀린의 시퀀스(Sequence) (2) | 2021.06.02 |
[Kotlin] 컬렉션(Collection)의 확장함수 (0) | 2021.05.27 |
[Kotlin] 컬렉션(Collection) - Set, Map (0) | 2021.05.26 |
[Kotlin] 컬렉션(Collection) - List (0) | 2021.05.24 |
[Kotlin] 문자열(String) (0) | 2021.05.21 |
댓글