일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 오블완
- 공부
- CKA
- minikube
- 정보처리기사 실기
- Java
- 코틀린 코루틴의 정석
- IntelliJ
- 정보처리기사실기 기출문제
- Spring
- kotlin querydsl
- CKA 기출문제
- AWS EKS
- AI
- kotlin coroutine
- mysql 튜닝
- Pinpoint
- Linux
- 기록으로 실력을 쌓자
- 정보처리기사 실기 기출문제
- Kubernetes
- MySQL
- kotlin
- kotlin spring
- APM
- Elasticsearch
- aws
- 티스토리챌린지
- CloudWatch
- PETERICA
- Today
- Total
피터의 개발이야기
[kotlin] 코틀린 코루틴의 정석 - 코루틴의 이해 본문
ㅁ 들어가며
ㅇ 코틀린 코루틴의 정석 책을 보고 정리한 글입니다.
10장 코루틴의 이해
- 루틴, 서브루틴, 코루틴의 이해
- 코루틴의 스레드 양보
- 고정적이지 않은 코루틴의 실행 스레드
ㅁ 서브루틴과 코루틴
ㅇ 서브루틴은 함수 안에서 호출되는 함수를 말한다.
ㅇ 일반적으로 루틴이라는 단어는 '특정한 일을 처리하는 과정'을 의미한다.
ㅇ 프로그래밍에서는 루틴을 '특정한 일을 처리하기 위한 일련의 명령'을 의미하며, 이런 명령을 함수 또는 메서드라고 한다.
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
//메인 루틴
routine(startTime)
}
fun routine(startTime:Long) {
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] main Routine")
// 서브 루틴
subRoutineA(startTime)
subRoutineB(startTime)
}
fun subRoutineA(startTime:Long) {
Thread.sleep(1000L);
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] subRoutineA")
}
fun subRoutineB(startTime:Long) {
Thread.sleep(1000L)
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] subRoutineB")
}
fun getElapsedTime(startTime: Long): String = "지난 시간: ${System.currentTimeMillis() - startTime}ms"
/*
[main @coroutine#1][지난 시간: 0ms] main Routine
[main @coroutine#1][지난 시간: 1012ms] subRoutineA
[main @coroutine#1][지난 시간: 2016ms] subRoutineB
*/
ㅇ 서브루틴은 루틴의 하위에서 실행되는 루틴이다. 즉, 함수 내부에서 호출되는 함수를 서브루틴이라고 한다.
ㅇ 서브루틴은 한 번 실행되면 끝까지 하나의 main 스레드에서 실행되면서, 순차적으로 실행되어 총 2초가 실행된다.
ㅇ 이 코드를 코루틴 형태로 바꾸어 보자
ㅁ 코루틴과 서브루틴의 차이
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
launch {
routine(startTime)
}
}
suspend fun routine(startTime:Long) = coroutineScope {
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] main Routine")
launch {
subRoutineA(startTime)
}
launch {
subRoutineB(startTime)
}
}
suspend fun subRoutineA(startTime:Long) {
delay(1000L);
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] subRoutineA")
}
suspend fun subRoutineB(startTime:Long) {
delay(1000L)
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] subRoutineB")
}
/*
[main @coroutine#2][지난 시간: 3ms] main Routine
[main @coroutine#3][지난 시간: 1019ms] subRoutineA
[main @coroutine#4][지난 시간: 1020ms] subRoutineB
*/
fun getElapsedTime(startTime: Long): String = "지난 시간: ${System.currentTimeMillis() - startTime}ms"
ㅇ 코루틴은 서로 간에 스레드 사용 권한을 양보하며 함께 실행된다.
ㅇ delay함수는 스레드를 양보하고 일정 시간 동안 코루틴을 일시 중단시킨다.
ㅇ 서브루틴은 스레드 양보를 통해 병렬처리가 가능하여 총 1초에 작업이 완료되었다.
ㅇ Thread.sleep은 스레드를 점유상태로 스레드는 다른 일을 할 수 없지만, delay를 통해 스레드 양보를 통해 스레드가 다른 작업을 수행할 수 있다.
ㅁ 코루틴의 스레드 양보
ㅇ 코루틴은 제어권 양보를 통해 스레드 자원을 공유한다.
ㅇ 코루틴이 스레드를 양보하기 위한 방법은 다음 3가지이다.
ㄴ delay 함수: 위의 코드가 delay 함수 이용
ㄴ join과 await 동작 방식
ㄴ yield 함수
join과 await 동작 방식
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val job = launch {
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] launch 코루틴 작업이 시작됐습니다")
delay(100L) // 대기
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] launch 코루틴 작업이 완료됐습니다")
}
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] runBlocking 코루틴이 곧 일시 중단 되고 메인 스레드가 양보됩니다")
job.join() // job 작업 대기
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] runBlocking이 메인 스레드에 분배돼 작업이 다시 재개됩니다")
}
fun getElapsedTime(startTime: Long): String = "지난 시간: ${System.currentTimeMillis() - startTime}ms"
/*
[main @coroutine#1][지난 시간: 2ms] runBlocking 코루틴이 곧 일시 중단 되고 메인 스레드가 양보됩니다
[main @coroutine#2][지난 시간: 11ms] launch 코루틴 작업이 시작됐습니다
[main @coroutine#2][지난 시간: 117ms] launch 코루틴 작업이 완료됐습니다
[main @coroutine#1][지난 시간: 118ms] runBlocking이 메인 스레드에 분배돼 작업이 다시 재개됩니다
*/
ㅇ join과 await 함수를 호출한 코루틴은 join이나 await의 대상이 된 코루틴의 작업이 완료될 때까지 스레드를 양보하고 일시 중단한다.
ㅇ job.join()의 경우 순서보장을 위해 job이 완료될 때까지 main 스레드가 다른 작업을 수행하지 않고 대기한다.
yield 함수
fun main() = runBlocking<Unit> {
launch {
repeat(5) { i ->
println("[${Thread.currentThread().name}] ${i} 서브 코루틴 실행")
yield() // 스레드 양보
}
}
repeat(5) { i ->
println("[${Thread.currentThread().name}] ${i} 코루틴 실행")
yield() // 스레드 양보
}
}
/*
[main @coroutine#1] 0 코루틴 실행
[main @coroutine#2] 0 서브 코루틴 실행
[main @coroutine#1] 1 코루틴 실행
[main @coroutine#2] 1 서브 코루틴 실행
[main @coroutine#1] 2 코루틴 실행
[main @coroutine#2] 2 서브 코루틴 실행
[main @coroutine#1] 3 코루틴 실행
[main @coroutine#2] 3 서브 코루틴 실행
[main @coroutine#1] 4 코루틴 실행
[main @coroutine#2] 4 서브 코루틴 실행
*/
ㅇ yield 함수는 스레드 사용 권한을 명시적으로 양보하고자 할 때 사용된다.
fun main() = runBlocking<Unit> {
val job = launch {
while (this.isActive) {
println("무한 작업 중")
}
}
delay(100L) // 100밀리초 대기(스레드 양보)
job.cancel() // 코루틴 취소가 job이 작업하는 동안 실행이 되지 않는다.
}
/*
무한 작업 중
무한 작업 중
무한 작업 중
...
*/
ㅇ delay함수가 메인 스레드를 양보하면 launch 코루틴이 실행된다.
ㅇ 하지만 명시적인 yield를 호출하지 않아 무한 작업에 빠지게 된다.
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val dispatcher = newFixedThreadPoolContext(2, "MyThread")
launch(dispatcher) {
repeat(5) { i ->
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] ${i} 코루틴 실행 일시중단")
delay(100L) // delay 함수를 통해 launch 코루틴을 100밀리초간 일시 중단한다.
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] ${i} 코루틴 실행 재개")
}
}
}
/*
[MyThread-1 @coroutine#2][지난 시간: 5ms] 0 코루틴 실행 일시중단
[MyThread-2 @coroutine#2][지난 시간: 122ms] 0 코루틴 실행 재개
[MyThread-2 @coroutine#2][지난 시간: 122ms] 1 코루틴 실행 일시중단
[MyThread-1 @coroutine#2][지난 시간: 227ms] 1 코루틴 실행 재개
[MyThread-1 @coroutine#2][지난 시간: 227ms] 2 코루틴 실행 일시중단
[MyThread-2 @coroutine#2][지난 시간: 333ms] 2 코루틴 실행 재개
[MyThread-2 @coroutine#2][지난 시간: 333ms] 3 코루틴 실행 일시중단
[MyThread-1 @coroutine#2][지난 시간: 438ms] 3 코루틴 실행 재개
[MyThread-1 @coroutine#2][지난 시간: 438ms] 4 코루틴 실행 일시중단
[MyThread-2 @coroutine#2][지난 시간: 543ms] 4 코루틴 실행 재개
*/
ㅇ 코루틴은 협력적 동작은 스레드 제어권을 양도하면서 다른 코루틴이 스레드를 사용할 수 있도록 한다.
ㅇ 코루틴이 스레드를 양보하면 코루틴은 일시 중단되며, 재개될 때 Coroutine Dispatcher 객체를 통해 다시 스레드에 보내진다.
ㅇ CoroutineDispatcher 객체는 스레드풀에서 유휴한 스레드를 할당하기 때문에 고정된 스레드가 아니다.
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val dispatcher = newFixedThreadPoolContext(2, "MyThread")
launch(dispatcher) {
repeat(5) { i ->
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] ${i} 코루틴 실행 일시중단")
Thread.sleep(100L) // delay를 Thread.sleep으로 수정
println("[${Thread.currentThread().name}][${getElapsedTime(startTime)}] ${i} 코루틴 실행 재개")
}
}
}
/*
[MyThread-1 @coroutine#2][지난 시간: 7ms] 0 코루틴 실행 일시중단
[MyThread-1 @coroutine#2][지난 시간: 116ms] 0 코루틴 실행 재개
[MyThread-1 @coroutine#2][지난 시간: 116ms] 1 코루틴 실행 일시중단
[MyThread-1 @coroutine#2][지난 시간: 221ms] 1 코루틴 실행 재개
[MyThread-1 @coroutine#2][지난 시간: 221ms] 2 코루틴 실행 일시중단
[MyThread-1 @coroutine#2][지난 시간: 326ms] 2 코루틴 실행 재개
[MyThread-1 @coroutine#2][지난 시간: 327ms] 3 코루틴 실행 일시중단
[MyThread-1 @coroutine#2][지난 시간: 428ms] 3 코루틴 실행 재개
[MyThread-1 @coroutine#2][지난 시간: 428ms] 4 코루틴 실행 일시중단
[MyThread-1 @coroutine#2][지난 시간: 532ms] 4 코루틴 실행 재개
*/
ㅇ delay를 Thread.sleep으로 수정하였다.
ㅇ 코루틴이 스레드를 양보하지 않으면 실행 스레드가 바뀌지 않는다.
ㅇ 코루틴 내부에서 Thread.sleep 함수를 사용하면 코루틴이 대기하는 시간 동안 스레드를 양보하지 않고 블로킹한다.
ㅁ 함께 보면 좋은 사이트
'Programming > Kotlin' 카테고리의 다른 글
[kotlin] 코틀린 코루틴의 정석 - CoroutineScope의 start 옵션 (0) | 2024.06.10 |
---|---|
[kotlin] 코틀린 코루틴의 정석 - 비동기 다중 스레드 시 공유 데이터 정합성 문제 (1) | 2024.06.08 |
[kotlin] 코틀린 코루틴의 정석 - 일시 중단 함수 (0) | 2024.06.05 |
[kotlin] 코틀린 코루틴의 정석- 예외처리 (0) | 2024.06.04 |
[kotlin] 코틀린 코루틴의 정석- 구조화된 동시성(Structured Concurrency) (0) | 2024.06.03 |