사용이 목적이라면 쉽지만 이해하긴 어려운 코루틴을 공부해보자
킹갓 제이슨은 말씀하셨다.
어떻게 코어를 효율적이고 효과적으로 사용할 것인가?
코어나 프로세서가 추가됨에 따라 실행시간이 항상 빨라지는가?
코루틴 학습을 통해 이해해보도록 하자.
학습 로드맵
Philipp Lackner 의 KOTLIN COROUTINES
https://www.youtube.com/playlist?list=PLQkwcJG4YTCQcFEPuYGuv54nYai_lwil_
Android Kotlin 코루틴
https://developer.android.com/kotlin/coroutines?hl=ko
Kotlin Docs 코루틴
https://kotlinlang.org/docs/coroutines-overview.html
용어 정리
- 프로세스: 실행 중인 애플리케이션 인스턴스
- 스레드 : 프로세스의 작업 단위
동시성(ConCurrency) vs 병렬성(Parallelism)
- 동시성 : 적어도 두 개의 스레드가 생성되어야 하고 스레드 간에 통신과 동기화가 필요하다. 코어는 1개 이지만 스레드 간에 빠르게 교차하면서 실행되기 때문에 동시 라고 느끼게 된다. 순서에 상관없이 결과가 결정적이어야 한다. 즉 결과가 달라진다면 동시성이 깨졌다는 뜻이다.
여기서 결과란 무엇일까? 값과 오류
- 병렬성 : 2개 이상의 코어가 있어야 한다. 병렬성도 동시성을 의미하지만 동시성과 차이는 실제 동시에 명령어를 실행할 수 있음을 말한다.
코루틴에 대해 공부하기 전에!
문제: 아래 코드를 실행하면 총 몇 초가 걸릴까?
fun main() {
runBlocking { //Block 내부의 모든 스코프 실행이 완료될 때까지 기다린다.
println("${Thread.activeCount()} threads active at the start")
//InteliJ IDEA 에서 어플리케이션을 실행하면 이미 두 개의 스레드가 작동중이다.
val time = measureTimeMillis { //총 걸리는 시간을 측정한다
createCoroutines(3)
//작업 3개를 생성한다.
}
println("${Thread.activeCount()} threads active at the start")
println("Took $time ms")
}
}
suspend fun createCoroutines(amount: Int) {
coroutineScope {
val jobs = mutableListOf<Job>()
repeat(amount) {
jobs += launch { // 1초 걸리는 작업
println("Started $it in ${Thread.currentThread()}")
delay(1000)
println("Finished $it in ${Thread.currentThread().name}")
}
}
}
}
결과
1초 걸리는 작업이 3개인데 거의 1초만에 작업이 완료되었다!
이렇게 신기한 코루틴이 무엇인지 알아보자
코루틴
코루틴은 실행을 일시 중지할 수 있는 함수
함수 호출 → 함수 종료까지 원래는 통제 불가능
but 코루틴을 사용하면 함수에 대한 제어권을 가질 수 있다!
기본적으로 스레드 안에 존재하지만 스레드에 얽매이지 않은 경량 스레드
루틴과 서브 루틴의 비대칭적인 관계를 코루틴은 완전히 대칭적인 관계로 만들어준다.
루틴을 이해하고 싶다면 추천합니당
- 쾌락코팅의 코루틴
스레드와 차이점
안드로이드는 기본적으로 Main(Ui스레드), 작업 스레드 사용
코루틴은 결과를 변경하지 않고 순서를 변경해 작업을 수행할 수 있다.
따라서 동시성을 제공하지만 동시에 여러 작업을 실행하지 않기 때문에 병렬성을 제공하지는 않는다.
코틀린 코루틴
- 최신 앱은 고성능 멀티코어 CPU 환경을 최대한 활용하도록 개발된다. 자바, 안드로이드 동시성 프레임워크가 제공하는 단점을 극복하고 넌 블로킹, 비동기 코드를 동기 코드처럼 쉽게 작성할 수 있다.
- 동시성이고 병렬성이 아니기 때문에 같은 스레드에 여러 개의 코루틴이 있다면 한 시점엔 하나의 코루틴만 실행된다.
- 코루틴은 운영체제의 지원을 필요로 하지 않기 때문에 빠르고 적은 비용으로 생성 가능하다.
- 스레드는 한 번에 하나의 코루틴만 실행할 수 있기 때문에 프레임워크가 필요에 따라 코루틴을 스레드들 사이에 옮기는 역할을 한다.(Dispatcher) 즉, 코루틴을 실행할 스레드를 지정하거나 코루틴을 해당 스레드로 제한할지 여부를 지정할 수 있어 유연하다.
이제 코드를 보면서 알아보자!
문제 : 다음 코드는 몇 초 걸릴까?
fun main() {
runBlocking {
val time = measureTimeMillis {
val name = getName()
val lastName = getLastName()
println("안녕하세요! $name $lastName 입니다.")
}
println("실행 시간 : $time ms")
}
}
suspend fun getName(): String {
println("getName 시작")
delay(1000)
println("getName 끝")
return "SeongHoon"
}
suspend fun getLastName(): String {
println("getLastName 시작")
delay(1000)
println("getLastName 끝")
return "Choi"
}
정답
약 2초의 결과가 나왔다.
runBlocking 내부에서 별도의 스코프를 열지 않았기 때문에 동기로 처리되고 따라서 2초라는 결과가 나온다.
이제 스코프를 열어보자. async 를 사용해서 열었다. async 는 순서에 상관없이 호출하겠다는 의미이다.
문제 : 다음 코드 실행 결과 몇 초가 출력될까?
fun main() {
runBlocking {
val time = measureTimeMillis {
val name = async { getName() }
val lastName = async { getLastName() }
println("안녕하세요! $name $lastName 입니다.")
}
println("실행 시간 : $time ms")
}
}
// ... 중복 코드
정답
1초가 나올 것이라고 예상했지만 엥? 0.008초의 결과가 나왔다.
출력된 결과를 보면 이름이 아닌 DefferedCoroutine 이 그대로 출력된 것을 확인할 수 있다.
즉 함수가 끝나고 이름을 반환한 것이 아닌 async 메서드의 반환값 Deferred 가 그대로 들어간 것이다.
runBlocking 은 스코프들의 실행 종료를 보장하기 때문에 print가 되고 나서 getName() 과 getLastName() 메서드가 끝날 때까지 기다린 후 종료된다.
따라서 실행은 되었지만 같은 결과를 얻지 못했기 때문에 동시성으라고 할 수 없다.
같은 결과를 얻으려면 async.await() 을 사용해야한다.
fun main() {
runBlocking {
val time = measureTimeMillis {
val name = async { getName() }
val lastName = async { getLastName() }
println("안녕하세요! ${name.await()} ${lastName.await()} 입니다.")
}
println("실행 시간 : $time ms")
}
}
결과
이렇게 코루틴을 사용하면 같은 결과를 적은 시간에 효율적으로 처리할 수 있다!
반환값이 없다면 launch 를 사용할 수 있다!
fun main() {
runBlocking {
launch { first() }
launch { second() }
}
}
suspend fun first() {
println("first 시작")
delay(1000)
println("first 끝")
}
suspend fun second() {
println("second 시작")
delay(1000)
println("second 끝")
}
하지만 반환값이 없기 때문에 위와 같이 걸린 시간을 체크하고 싶으면 launch {}.join 을 사용해야한다!
마지막 코치님의 질문!
과연 Job을 완료할때까지 기다리는 방법은 뭐가 있고 어떤 차이가 있는가?
완료 말고 취소시킬수도 있는가?
다음에 코루틴 2로 돌아오겠습니다~
'우아한테크코스' 카테고리의 다른 글
[안드로이드] App 로깅 전략 with Firebase Analytics [우아한테크코스 5기 AN_베르] (0) | 2023.08.06 |
---|---|
Kotlin coroutine 강의로 이해하기 - 2 : [우아한테크코스 5기 AN_베르] (0) | 2023.07.30 |
[안드로이드] MVP 패턴을 MVVM 패턴으로 리팩터링하기 (0) | 2023.07.09 |
Presenter 테스트 작성하기 (안드로이드 MVP) : [우아한테크코스 5기 AN_베르] (0) | 2023.06.23 |
오목 데코레이터 패턴 구현 : [우아한테크코스 5기 AN_베르] (0) | 2023.04.12 |