여러개의 실행 루틴을 동작시키는 멀티스레드 대신 좀 더 다루기 쉬운 코루틴을 사용한다.
=============
한개의 루틴을 완료한 후 다른 루틴을 실행하는 방식
여러개의 루틴이 선행 작업의 순서나 완료여부와 상관없이 실행되는 방식
co(함께 동시에) + routine (하나의 개별적인 작업)
넌블로킹 또는 비동기 코드를 동기코드처럼 쉽게 작성하면서도 비동기 효과를 낼 수 있다.
프로세스는 실행중인 프로그램 (os로 부터 필요한 자원을 받아 프로세스가 됨) , 실제로 작업을 수행하는 것이 스레드(프로세스 내에서 stack만 할당받고 코드 데이터 힙은 공유)
=> 코루틴은 문맥교환이 없고 최적화된 비동기 함수를 통해 비선점형으로 작동하는 특징이 있어 협력형 멀티태스킹을 구현할 수 있게 해준다.
+ 문맥교환: (하나의 태스크가 CPU를 사용하고 있는 상태에서 다른 태스크가 CPU를 사용하도록 하기위해 이전의 프로세스의 상태를 보관하고 새로운 프로세스의 상태를 적재하는 과정)
협력형 멀티태스킹 :(태스크들이 자발적으로 양보하며 실행을 바꿀수있는개념)
코루틴은 비용이 많이 드는 문맥교환 없이 해당 루틴을 **일시 중단**해서 이러한 비용을 줄일 수 있다.
=> 운영체제가 스케줄링에 개입하는 과정이 필요없다.
+
일시중단을 사용자가 제어할 수 있다.
kotlinx.coroutines.* 로 접근한다.
fun main() {
val job = GlobalScope.launch { // Job 객체의 반환
delay(1000L) println("World!")
}
println("Hello,")
println("job.isActive: ${job.isActive}, completed: ${job.isCompleted}") //코루틴의 상태 반환
Thread.sleep(2000L) println("job.isActive: ${job.isActive}, completed: ${job.isCompleted}")
}
코루틴의 생명주기는 프로그램의 생명주기에 의존된다.
suspend fun doWork1( ): String {
delay(1000) return "Work1"
}
suspend fun doWork2( ): String {
delay(3000) return "Work2"
}
private fun worksInSerial() { // 순차적 실행
GlobalScope.launch {
val one = doWork1( )
val two = doWork2( )
println("Kotlin One : $one")
println("Kotlin Two : $two")
}
}
fun main() {
worksInSerial( )
readLine( ) // main()이 먼저 종료되는 것을 방지하기
//실행 결과 Kotlin One : Work1 Kotlin Two : Work2
}
launch에 정의된 doWork1()과 doWork2() 함수는 순차적으로 표현 할 수 있다. 내부적으로 비동기 코드로서 동작 할 수 있지만 코드만 봤을때 순차적으로 실행되는 것처럼 표현함으로서 프로그래밍의 복잡도를 낮춘다.
코루틴 빌더를 생성할 수있는 또하나의 키워드
// 위의 코드 아래에 써져있다고 가정
private fun worksInParallel() {
// Deferred<T>를 통해 결괏값을 반환
val one = GlobalScope.async {
doWork1( )
}
val two = GlobalScope.async {
doWork2( )
}
GlobalScope.launch {
val combined = one.await( ) + "_" + two.await()
println("Kotlin Combined : $combined")
}
}
...
workInParellel( )
//실행 결과 Kotlin Combined : Work1_Work2
doWork1()과 2는 async에 의해 감싸져 있으므로 완전히 병행 수행 할 수 있다.
이 코드에서는 1초 더 지연시킨 doWork1이 먼저 종료될것이라고 예상 가능하다.
하지만 복잡한 루틴을 작성하는 경우에는 많은 태스크들과 같이 병행 수행되므로 어떤 루틴이 먼저 종료될지 알기 어려움
=> await를 사용해 태스크가 종료되는 시점을 기다렸다가 결과를 받도록한다. 그러면 현재 스레드의 블로킹 없이 먼저 종료되면 결과를 가져올 수 있다.
-사용 예시 : 안드로이드 UI스레드에서 블로킹 가능성이 있는 코드를 사용하면 앱이 중단되거나 멈출수 있음
이경우 await를 사용하면 ui를 제외한 루틴만 블로킹 되므로 ui멈춤을 해결할 수 있다.
코루린의 문맥은 CorutineContext에 의해 정의된다.
1. launch {..} 와 같이 인자가 없는 경우 CoroutineScope에서 상위의 context가 상속되어 결정되고
2. launch(Dispatchers.Default){..}와 같이 사용되면 GlobalScope에서 실행되는 context와 동일하게 사용된다.
코루틴이 사용할 스레드의 공동 pool을 사용한다. 이미 초기화 되어있는 스레드중에서 택하기 때문에 스레드를 생성하는 오버헤드가 없어 빠르다
val job=async(start=CoroutineStart.LAZY){doWork1()}
-> start()나 await()가 호출될때 실제 루틴이 시작됨
새로운 코루틴을 실행하고 완료되기 까지 현재 스레드를 블로킹한다.
앞에서는 메인 스레드가 종료되는것을 막기위해 readLine()을 했지만
메인 스레드 자체를 잡아두기 위해 main 자체를 블로킹 모드에서 실행 할 수있다.
fun main() = runBlocking<Unit> { // main() 함수가 코루틴 환경에서 실행
launch { // 백그라운드로 코루틴 실행
delay(1000L) println("World!")
}
println("Hello") // 즉시 이어서 실행됨
// delay(2000L)
// delay( ) 함수를 사용하지 않아도 main() 함수내부의 코루틴이 모두 작동할 때까지 블로킹
}
Job 객체의 join함수를 사용한다. job에서 지정한 작업이 완료될때까지 기다린다.
sequence() : 표준라이브러리중 하나. 아주 많은 값을 만들어 내는 코드로 부터 특정 값의 범위를 가져올 수 있다.
sequence() 내부에 지연함수를 사용해 코루틴과 함께 최종 형태를 나중에 결정할 수 있는 lazy 시퀀스를 만들 수 있다.
무한 스크롤링을 구현하는 ui에 적용할 목록을 가져올 때 이용할 수 있다~