지금까지는 이렇게 간주하고 학습했다.
하지만 사실은 이렇다. 이래야 리소스 사용률이 늘어나겠지..(feat.운영체제)
그러므로 concurrency를 신경써야 한다. task2 시작했다고 task1이 갑자기 중단된다면 매우 불안정한 앱이 될 것이다.
currentThread()
는 스레드의 이름, 우선순위, 스레드 그룹을 반환하는 함수이다. 이 함수를 가지고 간단한 동시 실행을 보자.
fun main() {
val states = arrayOf("Starting", "Doing Task 1", "Doing Task 2", "Ending")
repeat(3) {
Thread {
println("${Thread.currentThread()} has started")
for (i in states) {
println("${Thread.currentThread()} - $i")
Thread.sleep(50)
}
}.start()
}
}
여러개의 스레드작업이 출력되는 것을 볼 수 있다.
하지만 멀티스레딩 작업을 직접 구현하는 건 썩 권장되지 않는다.. 예를 들어 50개의 스레드가 차례대로 count라는 변수에 접근해 +1씩 한다고 치자.
fun main() {
var count = 0
for (i in 1..50) {
Thread {
count += 1
println("Thread: $i count: $count")
}.start()
}
}
쓰레드 43에서 갑자기 count가 50이 되어버렸다. 항상 순서대로 출력되지는 않기 때문에 이렇게 꼬일 수 있다. 동시에 메모리에 접근해서 결과가 이상해질 수도 있을 것이다.(race condition)
이러한 문제 때문에 코틀린은 동시 실행 코드 작성을 돕는 코루틴 이라는 기능을 제공한다.
코루틴은 멀티태스킹을 지원하지만 단순히 스레드로 작업하는 것보다 다른 수준의 추상화를 제공한다. 코루틴의 주요 기능 중 하나는 상태를 저장하여 중단했다가 재개할 수 있다는 것이다. continuations(연속) 으로 표현되는 상태로 다른 코루틴이 작업을 완료할 때까지 기다려야 하는 시기를 나타낼 수 있다.
연속 외에도 코루틴을 만드는 것에는 Job
의 작업이 포함된다.
Job
는 CoroutineScope
내에서 취소 가능한 작업 단위(예: launch() 함수로 만든 작업 단위)이다.
CoroutineScope
는 하위 요소와 그 하위 요소에 취소 및 기타 규칙을 반복적으로 적용하는 context이다.
Dispatcher
는 코루틴이 실행에 사용할 지원 스레드를 관리한다. 얘 덕분에 개발자가 각 스레드에 대해 일일히 신경쓰지 않아도 되고, 코루틴이 좋은 성능을 발휘할 수 있다.
간단히 코루틴을 사용하여보자.
import kotlinx.coroutines.*
fun main() {
repeat(3) {
GlobalScope.launch {
println("Hi from ${Thread.currentThread()}")
}
}
}
기본 Dispatcher
를 사용하여 Global Scope에서 코루틴 세 개를 만들었다.
launch()
함수는 취소 가능한 Job 객체에 래핑된 닫힌 코드에서 코루틴을 만든다.
fun CoroutineScope.launch {
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
}
참고로 launch()
함수 내부를 뜯어보면(?) 이렇게 생긴 듯 하다. 개발자가 전달한 코드 블록은 suspend
키워드로 표시된다.
fun main() {
val states = arrayOf("Starting", "Doing Task 1", "Doing Task 2", "Ending")
repeat(3) {
Thread {
println("${Thread.currentThread()} has started")
for (i in states) {
println("${Thread.currentThread()} - $i")
Thread.sleep(50)
}
}.start()
}
}
아까 이 코드를, 스레드 대신 코루틴을 사용하는 방향으로 바꿔보자.
import kotlinx.coroutines.* //임포트문 추가
fun main() {
val states = arrayOf("Starting", "Doing Task 1", "Doing Task 2", "Ending")
repeat(3) {
GlobalScope.launch { //launch로 바뀜.
println("${Thread.currentThread()} has started")
for (i in states) {
println("${Thread.currentThread()} - $i")
delay(5000) //delay로 바뀜.
}
} //start 빠짐
}
}
코루틴을 실제 안드로이드 앱에서 활용하는 예제는 Unit 4-2 참고하기!