일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 기출문제
- CloudWatch
- Kubernetes
- 티스토리챌린지
- 기록으로 실력을 쌓자
- Linux
- Java
- kotlin
- kotlin querydsl
- IntelliJ
- kotlin coroutine
- Elasticsearch
- AWS EKS
- 정보처리기사 실기 기출문제
- APM
- MySQL
- AI
- PETERICA
- 공부
- 코틀린 코루틴의 정석
- minikube
- 오블완
- Pinpoint
- CKA
- aws
- 정보처리기사실기 기출문제
- mysql 튜닝
- Spring
- kotlin spring
- Today
- Total
피터의 개발이야기
[kotlin] 코틀린 코루틴의 정석- 빌더와 Job 본문
ㅁ 들어가며
ㅇ 코틀린 코루틴의 정석 책을 보고 정리한 글입니다.
4장 코루틴 빌더와 Job
ㅁ 코루틴 빌더
// Job 객체 생성
val job: Job = launch(Dispatchers.IO) {
println("job은 생성된 코루틴의 상태를 추적하고 실행과 정지를 수행할 수 된다.")
}
ㅇ runBlocking 함수와 launch 함수는 코루틴을 만들기 위한 코루틴 빌더 함수이다.
ㅇ 코루틴 빌더는 코루틴을 추상화하나 Job 객체를 통해 코루틴의 상태를 추적하고 일시 중단 및 재실행을 할 수 있다.
ㅁ join함수를 이용한 순차처리
val firstJob = launch(Dispatchers.IO) {
println("[${Thread.currentThread().name}] firtst job 시작")
delay(100L)
println("[${Thread.currentThread().name}] firtst job 종료")
}
val dummyJob = launch(Dispatchers.Default) {
delay(1000)
println("[${Thread.currentThread().name}] 이미 스캐줄된 dummyJob은 일시정지 되지 않는다.")
}
firstJob.join() // firstJob을 우선처리하도록 대기한다.
val secondJob = launch(Dispatchers.IO) {
println("[${Thread.currentThread().name}] secondJob 시작")
}
/* 출력
[DefaultDispatcher-worker-1 @coroutine#2] firtst job 시작
[DefaultDispatcher-worker-1 @coroutine#2] firtst job 종료
[DefaultDispatcher-worker-1 @coroutine#4] secondJob 시작
[DefaultDispatcher-worker-1 @coroutine#3] 이미 스캐줄된 dummyJob은 일시정지 되지 않는다.
*/
ㅇ join 함수를 호출하면 해당 코루틴이 완료될 때까지 다른 루틴의 스케줄을 수행하지 않는다.
ㅇ 기존 스케줄된 dummyJob은 일시 중단하지 않는다.
ㅁ joinAll, 병렬처리 순서 보장
// 구현체에는 다중으로 받은 job forEach로 join을 걸어준다.
public suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach { it.join() }
결국 joinAll(job1, job2)는
job1.join()
job2.join()
와 동일한 작업이다.
ㅇ 병렬처리가 필요한 다중 Job의 순서 보장은 joinAll을 이용한다.
ㅇ joinAll 함수를 사용해 복수의 코루틴이 실행 완료될 때까지 대기할 수 있다.
ㅁ 지연시작, CoroutineStart.LAZY
fun main() = runBlocking<Unit> {
val batchJob: Job = launch(start = CoroutineStart.LAZY) {
println("[${Thread.currentThread().name}] 배치작업 실행")
}
println("[${Thread.currentThread().name}] 메인작업 실행")
delay(1000L) // 1초간 대기
batchJob.start() // 코루틴 실행
}
/* 출력
[main @coroutine#1] 메인작업 실행
[main @coroutine#2] 배치작업 실행
*/
ㅇ 작업 중에 우선 순위가 낮고 오래 걸리는 배치성 업무가 있을 수 있다.
ㅇ 서비스 지연을 예방하기 위해 메인작업 이후에 실행시킬 수 있다.
ㅇ LAZY로 정의된 루틴은 start()로 실행된다.
ㅇ 참고: CoroutineStart의 다른 옵션은 [kotlin] 코틀린 코루틴의 정석 - CoroutineScope의 start 옵션에 정리함.
ㅁ cancel
fun main() = runBlocking<Unit> {
val longJob: Job = launch(Dispatchers.Default) {
repeat(10) { i ->
delay(500L) // 500밀리초 대기
println("${getData()} ${i}")
}
}
delay(2000) // 지연
longJob.cancel() // 코루틴 취소
}
fun getData(): String = "[${SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SS").format(System.currentTimeMillis())}]"
/*
// 결과:
[2024-05-23 01:11:13.280] 0
[2024-05-23 01:11:13.795] 1
[2024-05-23 01:11:14.301] 2
*/
ㅇ Job의 cancel()를 사용해 코루틴에 취소할 수 있다.
ㅇ cancel은 수행 중인 루틴이 바로 취소되지 않고, 코루틴의 취소 플래그가 바뀌면 취소가 된다.
ㅁ cancelAndJoin
// cancel을 했지만 루틴은 지속된다.
fun main() = runBlocking<Unit> {
val cancelJob: Job = launch(Dispatchers.Default) {
var i = 0
while(true) {
println("${i++} doing")
}
}
delay(2000) // 100밀리초 대기
cancelJob.cancel() // 코루틴 취소
}
/*
// 결과:
...
17203094 doing
17203095 doing
17203096 doing
17203097 doing
종료 코드 130 (interrupted by signal 2:SIGINT)(으)로 완료된 프로세스
*/
ㅇ 취소가 되어도 루틴이 즉시 멈추지 않을 수 있다.
ㅇ cancel을 해도 코루틴이 취소를 확인할 수 없는 상태에서는 계속 실행된다.
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val cancelJob: Job = launch(Dispatchers.Default) {
var i = 0
while(true) {
println("rest 시점에 cancel이 수행된다.")
delay(10) // 루틴이 잠시 휴식
repeat(10000){ // 반복작업이 길어지는 만큼 cancel의 시간도 늘어난다.
println("${getData(System.currentTimeMillis())} ${i++} doing")
}
}
}
delay(30) // 대기
cancelJob.cancel() // 코루틴 취소
println("${getData(startTime)} 시작시간")
}
fun getData(time:Long): String = "[${SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SS").format(time)}]"
/*
rest 시점에 cancel이 수행된다.
[2024-05-23 02:01:08.102] 시작시간
[2024-05-23 02:01:08.120] 0 doing
[2024-05-23 02:01:08.152] 1 doing
[2024-05-23 02:01:08.152] 2 doing
~~~~~
[2024-05-23 02:01:08.223] 9996 doing
[2024-05-23 02:01:08.223] 9997 doing
[2024-05-23 02:01:08.223] 9998 doing
[2024-05-23 02:01:08.223] 9999 doing
rest 시점에 cancel이 수행된다.
*/
ㅇ 루틴이 잠시 휴식하는 경우 cancel이 정상적으로 작동하였다.
ㅇ 이처럼 cancel은 스레드의 유휴상태에 따라 작동 시간을 보장할 수 없다.
ㅇ 취소되는 시점에 다음 루틴이 실행되려면 cancelAndJoin 함수를 사용해야한다.
fun main() = runBlocking<Unit> {
val startTime = System.currentTimeMillis()
val cancelJob: Job = launch(Dispatchers.Default) {
var i = 0
while(true) {
println("rest 시점에 cancel이 수행된다.")
delay(10) // 루틴이 잠시 휴식
repeat(100000){ // 반복작업이 길어지는 만큼 cancel의 시간도 늘어난다.
println("${getData(System.currentTimeMillis())} ${i++} doing")
}
}
}
delay(30)
cancelJob.cancelAndJoin() // <<<<<<< 취소 시 순서 보장하도록 변경
println("${getData(startTime)} 시작시간")
}
fun getData(time:Long): String = "[${SimpleDateFormat("yyyy-MM-dd hh:mm:ss.SS").format(time)}]"
/*
~~~~~
[2024-05-23 02:07:10.176] 99993 doing
[2024-05-23 02:07:10.176] 99994 doing
[2024-05-23 02:07:10.176] 99995 doing
[2024-05-23 02:07:10.176] 99996 doing
[2024-05-23 02:07:10.176] 99997 doing
[2024-05-23 02:07:10.176] 99998 doing
[2024-05-23 02:07:10.176] 99999 doing
rest 시점에 cancel이 수행된다.
[2024-05-23 02:07:09.875] 시작시간
*/
ㅇ 시작시간 로그가 마지막에 나타났다.
ㅇ cancelJob이 취소 완료된 후에 시작시간 로그가 나타났다.
ㅁ cancel을 만드는 세가지 방법
ㅇ delay, yield 함수나 isActive 프로퍼티 등을 사용해 코루틴이 취소를 확인할 수 있도록 만들 수 있다.
delay, yield
fun main() = runBlocking<Unit> {
val cancelJob: Job = launch(Dispatchers.Default) {
while(true) {
println("doing job")
// delay(1)
// yield()
}
}
delay(10)
cancelJob.cancel()
}
ㅇ delay와 yield로 cancel를 수행할 수 있다.
isActive
fun main() = runBlocking<Unit> {
val cancelJob: Job = launch(Dispatchers.Default) {
while(this.isActive) { //
println("doing job")
}
}
delay(10)
cancelJob.cancel()
}
ㅇ cancel을 지원하려면 장기 실행 루프에서 isActive 속성을 확인해야한다.
ㅁ 코루틴의 상태
ㅇ 코루틴은 생성, 실행 중, 실행 완료 중, 실행 완료, 취소 중, 취소 완료 상태를 가진다.
ㅇ 생성(New) : 코루틴 빌러를 통해 생성된 상태.
ㅇ 실행중(Active) : CoroutineStart.Lazy가 아니면 자동으로 실행 상태가 된다.
ㅇ 실행완료(Completed) : 모든 코드가 실행 완료된 상태
ㅇ 취소 중(Cancelling) : Job.cancel()등을 통해 취소가 요청된 상태로 넘어가는 중
ㅇ 취소 완료(Cancelled) : 코루틴의 취소 확인 시점에 취소가 된 경우
ㅁ Job의 상태변수
Coroutine State | isActive | isCancelled | isCompleted |
New | false | false | false |
Active | true | false | false |
Completed | false | false | true |
Cancelling | false | true | false |
Cancelled | false | true | true |
ㅇ Job 객체는 isActive, isCancelled, isCompleted 프로퍼티를 통해 코루틴의 상태를 나타낸다.
fun main() = runBlocking<Unit> {
val jobStateChk = launch(start = CoroutineStart.LAZY) { // 생성 상태의 Job 생성
delay(1000)
}
jobStateChk.start()
println(">> job is started!!!!")
println("isActive >> ${jobStateChk.isActive}")
println("isCancelled >> ${jobStateChk.isCancelled}")
println("isCompleted >> ${jobStateChk.isCompleted}" )
println()
jobStateChk.cancel()
println(">> job is cancel!!!!")
println("isCancelled >> ${jobStateChk.isCancelled}")
println()
jobStateChk.start()
joinAll(jobStateChk)
println(">> job is done!!!!")
println("isCompleted >> ${jobStateChk.isCompleted}" )
}
/*
>> job is started!!!!
isActive >> true
isCancelled >> false
isCompleted >> false
>> job is cancel!!!!
isCancelled >> true
>> job is done!!!!
isCompleted >> true
*/
ㅇ 생성 상태일 때 isActives = false
ㅇ 실행되면 isActives = true
ㅇ cancel 혹은 실행 완료 시 false
ㅇ isCancelled는 코루틴이 취소 중이거나 취소 완료됐을 때만 true
ㅇ isCompleted는 코루틴이 취소 완료되거나 실행 완료 됐을 때만 true
ㅇ 코루틴 라이브러리를 효율적으로 사용하기 위해서는 코루틴의 상태변화를 이해하는 것이 중요하다.
ㅁ 함께 보면 좋은 사이트
'Programming > Kotlin' 카테고리의 다른 글
[kotlin] 코틀린 코루틴의 정석- CoroutineContext, 코루틴 실행환경 설정 (0) | 2024.06.02 |
---|---|
[kotlin] 코틀린 코루틴의 정석- Async-await와 withContext 비교 (0) | 2024.06.01 |
[kotlin] 코틀린 코루틴의 정석- CoroutineDispatcher (0) | 2024.05.29 |
[kotlin] 코틀린 코루틴의 정석- 스레드 기반 작업의 한계와 코루틴의 등장 (0) | 2024.05.28 |
[kotlin] Kotlin Coroutine의 비동기 처리 장점, RxKotlin의 Callback 지옥 (0) | 2024.05.27 |