일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- AI
- Spring
- kotlin querydsl
- APM
- 정보처리기사 실기 기출문제
- kotlin coroutine
- kotlin
- CKA 기출문제
- 정보처리기사 실기
- Linux
- CloudWatch
- minikube
- CKA
- MySQL
- Kubernetes
- 정보처리기사실기 기출문제
- 기록으로 실력을 쌓자
- IntelliJ
- Elasticsearch
- Java
- PETERICA
- Pinpoint
- 오블완
- aws
- 티스토리챌린지
- AWS EKS
- kotlin spring
- 코틀린 코루틴의 정석
- 공부
- mysql 튜닝
- Today
- Total
피터의 개발이야기
[kotlin] 코틀린 코루틴의 정석- CoroutineDispatcher 본문
ㅁ 들어가며
ㅇ 코틀린 코루틴의 정석 책을 보고 정리한 글입니다.
2장 정리
ㅇ 현재 실행 중인 스레드의 이름 출력: Thread.currentThread().name
ㅇ 스레드의 이름 출력: JVM의 VM options에 -Dkotlinx.coroutines.debug
ㅇ CoroutineName으로 코루틴의 이름을 지정
ㅁ 실행 중인 코루틴의 이름 출력 VM option
-Dkotlinx.coroutines.debug
[main @coroutine#1] Start]
종료 코드 0(으)로 완료된 프로세스
ㅇ 스레드 출력 시 코루틴의 이름이 표출된다.
ㅁ launch로 개별 스레트 할당하기
fun main() = runBlocking<Unit> {
println("[${Thread.currentThread().name}] 실행1")
launch {
println("[${Thread.currentThread().name}] 실행2")
}
launch {
println("[${Thread.currentThread().name}] 실행3")
}
}
/*
[main @coroutine#1] 실행1
[main @coroutine#2] 실행2
[main @coroutine#3] 실행3
*/
ㅇ main과 2개의 launch가 생성한 스래드에 의해 병렬처리가 되었다.
ㅁ Coroutine 이름 지정
...
fun main() = runBlocking<Unit>(context = CoroutineName("Peterica")) {
...
ㅇ CoroutineName을 Peterica로 지정하였다.
3장 CoroutineDispatcher
ㅇ 스레프풀 스케줄러
Dispatcher의 뜻은 무언가를 보내는 주체를 말한다. CoroutineDispatcher는 스레드풀을 생성하여 코루틴을 스레드로 보내 실행시키는 역할을 한다. 자신에게 실행 요청된 코루틴들을 큐에 적재하고, 유휴스레드에 스케줄링하는 역할을 한다.
ㅇ CoroutineDispatcher의 종류
CoroutineDispatcher는 스레드풀을 제한하는 제한된 디스패처와 무제한 디스패처가 있다.
ㅁ 제한된 디스패처 만들기
// Single
val dispatcher: CoroutineDispatcher = newSingleThreadContext(name = "SingleThread")
// multi
val multiThreadDispatcher: CoroutineDispatcher = newFixedThreadPoolContext(
nThreads = 2,
name = "MultiThread"
)
ㅁ launch로 dispatcher 사용하기
val dispatcher = newSingleThreadContext(name = "SingleThread")
launch(dispatcher) {
println("[${Thread.currentThread().name}] SingleThread라고 dispatcher의 스레드 이름이 보인다.")
}
// 출력
[SingleThread] SingleThread라고 dispatcher의 스레드 이름이 보인다.
종료 코드 0(으)로 완료된 프로세스
ㅇ 스레드풀이 생성된 dispatcher를 받아 launch가 println을 실행시킨다.
ㅁ launch로 multi dispatcher 사용하기
val multiThreadDispatcher = newFixedThreadPoolContext(
nThreads = 2,
name = "MultiThread"
)
launch(context = multiThreadDispatcher) {
delay(1000) // 지연발생
println("[${Thread.currentThread().name}] 실행1")
}
launch(context = multiThreadDispatcher) {
delay(1100) // 지연발생
println("[${Thread.currentThread().name}] 실행2")
}
launch(context = multiThreadDispatcher) {
println("[${Thread.currentThread().name}] 실행3")
}
/* 출력
[MultiThread-1] 실행3
[MultiThread-2] 실행1
[MultiThread-1] 실행2
*/
ㅇ dispatcher가 개별 스레드 잡에 지연이 발생하면 스케줄관리를 하여 실행3이 우선 실행하도록 한다.
ㅁ 부모와 자식루틴
val dataFormat = SimpleDateFormat("yyyy-MM-dd hh:mm:ss") // 시간 로그용
val multiThreadDispatcher = newFixedThreadPoolContext(
nThreads = 2,
name = "MultiThread"
)
launch(multiThreadDispatcher) { // 부모 Coroutine
println("[${Thread.currentThread().name}] 부모 코루틴 실행")
val childThread = newFixedThreadPoolContext(
nThreads = 2,
name = "childThread"
)
// 자식 코루틴 실행
for (i in 1..20) {
launch(childThread) {
delay(i.toLong()*100)
println("[${dataFormat.format(System.currentTimeMillis())}][${Thread.currentThread().name}] 자식 코루틴 실행${i}")
}
}
}
/* 출력
[MultiThread-1 @coroutine#2] 부모 코루틴 실행
[2024-05-22 04:58:18][childThread-2 @coroutine#3] 자식 코루틴 실행1
[2024-05-22 04:58:18][childThread-1 @coroutine#4] 자식 코루틴 실행2
[2024-05-22 04:58:18][childThread-2 @coroutine#5] 자식 코루틴 실행3
[2024-05-22 04:58:18][childThread-1 @coroutine#6] 자식 코루틴 실행4
[2024-05-22 04:58:18][childThread-2 @coroutine#7] 자식 코루틴 실행5
[2024-05-22 04:58:18][childThread-1 @coroutine#8] 자식 코루틴 실행6
[2024-05-22 04:58:18][childThread-2 @coroutine#9] 자식 코루틴 실행7
[2024-05-22 04:58:19][childThread-1 @coroutine#10] 자식 코루틴 실행8
[2024-05-22 04:58:19][childThread-2 @coroutine#11] 자식 코루틴 실행9
[2024-05-22 04:58:19][childThread-1 @coroutine#12] 자식 코루틴 실행10
[2024-05-22 04:58:19][childThread-2 @coroutine#13] 자식 코루틴 실행11
[2024-05-22 04:58:19][childThread-1 @coroutine#14] 자식 코루틴 실행12
[2024-05-22 04:58:19][childThread-2 @coroutine#15] 자식 코루틴 실행13
[2024-05-22 04:58:19][childThread-1 @coroutine#16] 자식 코루틴 실행14
[2024-05-22 04:58:19][childThread-2 @coroutine#17] 자식 코루틴 실행15
[2024-05-22 04:58:19][childThread-1 @coroutine#18] 자식 코루틴 실행16
[2024-05-22 04:58:19][childThread-2 @coroutine#19] 자식 코루틴 실행17
[2024-05-22 04:58:20][childThread-1 @coroutine#20] 자식 코루틴 실행18
[2024-05-22 04:58:20][childThread-2 @coroutine#21] 자식 코루틴 실행19
[2024-05-22 04:58:20][childThread-1 @coroutine#22] 자식 코루틴 실행20
*/
ㅇ 부모 루틴에서 자식 루틴을 20개 실행해 보았다.
ㅇ 생성된 루틴은 coroutine#{num}로 인덱싱 되어 큐에 적재되고 순차적으로 유휴 스레드에 할당되어 처리되었다.
ㅁ Dispatchers의 종류와 용도
ㅇ Dispatchers.IO : 네트워크 및 파일IO
ㅇ Dispatchers.Default: CPU 작업
ㅇ Dispatchers.Main : 메인 스레드를 사용
ㅁ Dispatchers.IO
// 지연이 있는 경우
var a = 1
for(k in 1..20) {
a = 1
launch(Dispatchers.IO) {
delay(k.toLong()*50) // 지연발생 시
for(n in 1..100000) {
a = a * n * n
}
println("[${Thread.currentThread().name}] ${k}실행")
}
}
/* 출력
[DefaultDispatcher-worker-2 @coroutine#2] 1실행
[DefaultDispatcher-worker-2 @coroutine#3] 2실행
[DefaultDispatcher-worker-2 @coroutine#4] 3실행
[DefaultDispatcher-worker-2 @coroutine#5] 4실행
[DefaultDispatcher-worker-2 @coroutine#6] 5실행
[DefaultDispatcher-worker-2 @coroutine#7] 6실행
[DefaultDispatcher-worker-2 @coroutine#8] 7실행
[DefaultDispatcher-worker-2 @coroutine#9] 8실행
[DefaultDispatcher-worker-2 @coroutine#10] 9실행
[DefaultDispatcher-worker-2 @coroutine#11] 10실행
[DefaultDispatcher-worker-2 @coroutine#12] 11실행
[DefaultDispatcher-worker-2 @coroutine#13] 12실행
[DefaultDispatcher-worker-2 @coroutine#14] 13실행
[DefaultDispatcher-worker-2 @coroutine#15] 14실행
[DefaultDispatcher-worker-2 @coroutine#16] 15실행
[DefaultDispatcher-worker-2 @coroutine#17] 16실행
[DefaultDispatcher-worker-2 @coroutine#18] 17실행
[DefaultDispatcher-worker-2 @coroutine#19] 18실행
[DefaultDispatcher-worker-2 @coroutine#20] 19실행
[DefaultDispatcher-worker-2 @coroutine#21] 20실행
*/
// 지연이 없는 경우
var a = 1
for(k in 1..20) {
a = 1
launch(Dispatchers.IO) {
// delay(k.toLong()*50)
for(n in 1..100000) {
a = a * n * n
}
println("[${Thread.currentThread().name}] ${k}실행")
}
}
/* 출력
[DefaultDispatcher-worker-4 @coroutine#7] 6실행
[DefaultDispatcher-worker-17 @coroutine#17] 16실행
[DefaultDispatcher-worker-11 @coroutine#19] 18실행
[DefaultDispatcher-worker-12 @coroutine#11] 10실행
[DefaultDispatcher-worker-19 @coroutine#18] 17실행
[DefaultDispatcher-worker-18 @coroutine#15] 14실행
[DefaultDispatcher-worker-5 @coroutine#6] 5실행
[DefaultDispatcher-worker-25 @coroutine#20] 19실행
[DefaultDispatcher-worker-20 @coroutine#21] 20실행
[DefaultDispatcher-worker-16 @coroutine#16] 15실행
[DefaultDispatcher-worker-9 @coroutine#9] 8실행
[DefaultDispatcher-worker-2 @coroutine#4] 3실행
[DefaultDispatcher-worker-6 @coroutine#5] 4실행
[DefaultDispatcher-worker-8 @coroutine#10] 9실행
[DefaultDispatcher-worker-1 @coroutine#2] 1실행
[DefaultDispatcher-worker-7 @coroutine#8] 7실행
[DefaultDispatcher-worker-10 @coroutine#13] 12실행
[DefaultDispatcher-worker-3 @coroutine#3] 2실행
[DefaultDispatcher-worker-13 @coroutine#12] 11실행
[DefaultDispatcher-worker-14 @coroutine#14] 13실행
*/
ㅇ Dispatchers.IO는 공유 스레드를 사용하고 있는데, 얼마 정도 생성되어 있는지 알아보기 위해 루틴을 1000개 만들어 보았다.
var a = 1
// 1000개 루틴 생성
for(k in 1..1000) {
a = 1
launch(Dispatchers.IO) {
for(n in 1..100000) {
a = a * n * n
}
println("[${Thread.currentThread().name}] ${k}실행")
}
}
....
[DefaultDispatcher-worker-44 @coroutine#416] 415실행
[DefaultDispatcher-worker-65 @coroutine#417] 416실행
[DefaultDispatcher-worker-47 @coroutine#429] 428실행
[DefaultDispatcher-worker-72 @coroutine#430] 429실행 << 최대
[DefaultDispatcher-worker-7 @coroutine#432] 431실행
[DefaultDispatcher-worker-31 @coroutine#433] 432실행
[DefaultDispatcher-worker-65 @coroutine#435] 434실행
[DefaultDispatcher-worker-47 @coroutine#436] 435실행
[DefaultDispatcher-worker-35 @coroutine#419] 418실행
....
ㅇ Dispatchers.IO의 최대 스레드의 수 = JVM에서 사용이 가능한 프로세서의 수와 64 중 큰값
ㅇ 루틴을 1000개 정도 잡을 돌려보았을 때에 72개가 Max로 잡혔다.
ㅁ Dispatchers.Default
ㅇ 대용량데이터를 처리하기 위해 CPU 사용량에 맞추어진 스레드풀이다.
ㅇ 입출력작업과 CPU바운드작업
ㄴ 대용량데이터 처리 시 스레드를 최대로 사용하기 때문에 코루틴과 기존 스레드 방식은 차이가 거의 없다.
ㄴ 코루틴의 유휴스레드를 최대한 재사용하기 위해 스케줄하지만 유휴 스레드가 없다면 작업은 지연될 수 밖에 없다.
ㅁ limitedParallelism
launch(Dispatchers.Default.limitedParallelism(2)){
repeat(1000) {
launch {
println("[${Thread.currentThread().name}] 코루틴 실행")
}
}
}
출력:
[DefaultDispatcher-worker-2 @coroutine#3] 코루틴 실행
[DefaultDispatcher-worker-2 @coroutine#4] 코루틴 실행
[DefaultDispatcher-worker-2 @coroutine#5] 코루틴 실행
[DefaultDispatcher-worker-2 @coroutine#6] 코루틴 실행
~~~~~
[DefaultDispatcher-worker-2 @coroutine#999] 코루틴 실행
[DefaultDispatcher-worker-1 @coroutine#1000] 코루틴 실행
[DefaultDispatcher-worker-2 @coroutine#1001] 코루틴 실행
[DefaultDispatcher-worker-1 @coroutine#1002] 코루틴 실행
ㅇ limitedParallelism는 Dispatchers.Default의 스레드들의 과점유를 방지하기 위해 일부 스레드만 사용하도록 제한을 둔다.
ㅁ 공유스레드
코루틴 라이브러리는 스레드의 생성과 관리를 효율성을 위해 애플리케이션 레벨의 공유 스레드풀을 제공한다. 이 공유 스레드를 Dispatchers.IO와 Dispatchers.Default가 사용하며, 공유 스레드풀에서 일부 limitedParallelism을 이용해 사용한다.
다만, Dispatchers.IO.limitedParallelism(100)의 경우 공유스레드풀보다 더 많이 생성될 수 있다. IO가 많을 경우 추가적인 스레드를 할당할 수 있게 한 것이다. 이 방법은 기존 스레드에 영향을 주지 않고 신규로 스레드를 생성할 수 있지만, 생성에 따른 리소스로 CPU 병목이 있을 수 있다.
ㅁ 함께 보면 좋은 사이트
'Programming > Kotlin' 카테고리의 다른 글
[kotlin] 코틀린 코루틴의 정석- Async-await와 withContext 비교 (0) | 2024.06.01 |
---|---|
[kotlin] 코틀린 코루틴의 정석- 빌더와 Job (1) | 2024.05.30 |
[kotlin] 코틀린 코루틴의 정석- 스레드 기반 작업의 한계와 코루틴의 등장 (0) | 2024.05.28 |
[kotlin] Kotlin Coroutine의 비동기 처리 장점, RxKotlin의 Callback 지옥 (0) | 2024.05.27 |
[kotlin] Springboot - Configuration properties (0) | 2024.05.25 |