01. [Basic] 1부.코루틴과 동시성 프로그래밍
1-0 처음만나는코루틴
코루틴이란 ?
- 실행의 지연과 재개를 허용하면서, 비 선점적 멀티테스킹을 위한 설계 패턴.
코루틴은 비 선점형 멀티태스킹이고, 쓰레드는 선점형 멀티태스킹이다.
이 말은 즉 코루틴은 병행성(Concurrency)을 제공하지만 병렬성(Parallelism)을 제공하지 않는 다는 의미이다.
아니 그럼 병행성과 병렬성의 차이는 무엇인가?
병행성(Concurrency)
- 동시에 실행되는 것처럼 보이는 것.
- Logical Level에 속한다.
- Single Core 사용
- 물리적으로 병렬이 아닌 순차적으로 동작할 수 있다.
- 실제로는 Time-sharing으로 CPU를 나눠 사용함으로써 사용자가 Concurrency를 느낄 수 있도록 한다.
병렬성(Parallelism)
- 실제로 동시에 작업이 처리가 되는 것.
- Physical(Machine) Level에 속한다.
- 오직 Multi Core에서만 가능하다.
이 때 동시성(Concurrency)과 병렬성(Parallelism)에 대해서 설명드릴 수 있는데요!
동시성(Concurrency)은 특정 순간에 동시에 작업이 수행되는 것이 아니라,
마치 여러가지 일을 동시에 하는 것처럼 느껴지게 하기 위하여 시분할 처리를 하는 것을 의미합니다.
반면에 병렬성(Parallelism)은 실제 순간에 동시에 작업이 수행되어 병렬로 수행되는 것을 의미하죠.
코루틴의 특징
- 동시성을 지원하는 기술이다.
- Rx보다 코드가 단순하다.
- Rx보다 코드가 단순하다.
- 코루틴은 비동기와 동시성을 순차적으로 만들어낼수 있도록 도와준다.
- FLOW는 비동기와 병렬성을 Stream의 형태로 풀어낼수 있도록 도와준다.
- 코루틴에서의 핵심은 경량쓰레드를 어떻게 관리할 것인지 입니다 .
(이를 담당하는 것이 바로 Dispatcher 가 담당하게 됩니다)
- 경량쓰레드라고 불리기도 한다
( 코루틴은 Context switching이 발생하지 않고 메모리 사용을 줄인 채로
동시성을 보장할 수 있기 때문에 경량 스레드라고 표현하고 있습니다.)
- 구조화된 동시성(Structured Concurrency)을 구현하기 위한 기반 요소가 된다.
1-1 스코프빌더(1)
코루틴 빌더 ( 코루틴을 만드는 함수 )
=> 코틀린은 코루틴 빌더에 원하는 동작을 람다로 넘겨 코루틴을 실행하는 방식을 사용한다 .
종류
runBlocking ( 코루틴 빌더 )
- 코루틴을 만들고 코드 블록이 수행이 끝날 때까지, runBlocking 다음의 함수를 수행하지 못하게 막습니다.
( 그래서 블록킹이다. )
BlockingCoroutine은 CoroutineScope의 자식입니다.
코틀린 코루틴을 쓰는 모든 곳에는 코루틴 스코프(CoroutineScope)가 있다고 생각하면 됩니다.
코루틴의 시작은 코루틴 스코프다. 외웁시다.
* 현재 어떤 쓰레드를 사용하는지 보는 명령어 - Thread.currentThread().name
코루틴 컨텍스트
코루틴 스코프는 코루틴을 제대로 처리하기 위한 정보, 코루틴 컨텍스트(CoroutineContext)를 가지고 있습니다.
launch ( 코루틴 빌더 )
- 새로운 코루틴을 만들어준다. ( 즉, 새로운 코루틴 스코프를 만들어낸다. )
- 할 수 있다면 다른 코루틴 코드를 같이 수행 시키는 코루틴 빌더.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("launch: ${Thread.currentThread().name}")
println("World!")
}
println("runBlocking: ${Thread.currentThread().name}")
println("Hello")
}
수행 결과
runBlocking: main @coroutine#1
Hello
launch: main @coroutine#2
World!
launch 코루틴 빌더에 있는 내용이 runBlocking이 있는 메인 흐름 보다 늦게 수행된 것을 볼 수 있습니다.
둘 다 메인 스레드(main)를 사용하기 때문에
runBlocking의 코드들이 메인 스레드를 다 사용할 때 까지
launch의 코드 블록이 기다리는 것입니다.
runBlocking은 Hello를 출력하고 나서 종료하지는 않고
launch 코드블록의 내용이 다 끝날 때까지 기다립니다.
( launch 코드블록을 큐에 넣어두고 다음순서를 기다리고 있다고 보면 된다. )
* launch 를 이용해 동시에 여러코드를 수행 하는것 ( 디스패치 )
delay 함수 ( Suspension Point - 중단점 )
- 해당 스레드를 해제를 하고, 잠시 쉬어가는 형대로 되어있다
( Thread Sleep 과 다른 점
=> Thread.sleep을 하면 코루틴이 아무 일을 하지 않는 동안에도 스레드를 양보하지 않고 독점합니다. )
- 그 해제된 쓰레드를 다른 코루틴이 사용할 수 있도록 양보하는 구조다.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("launch: ${Thread.currentThread().name}")
println("World!")
}
println("runBlocking: ${Thread.currentThread().name}")
delay(500L)
println("Hello")
}
실행결과
runBlocking: main @coroutine#1
launch: main @coroutine#2
World!
Hello
프린트 문이 호출된 이후 delay가 호출되는데 이때 runBlocking의 코루틴이 잠이 들게 되고
launch의 코드 블록이 먼저 수행됩니다.
1-1 스코프빌더(2)
Thread.sleep을 하면?
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("launch: ${Thread.currentThread().name}")
println("World!")
}
println("runBlocking: ${Thread.currentThread().name}")
Thread.sleep(500)
println("Hello")
}
실행결과
runBlocking: main @coroutine#1
Hello
launch: main @coroutine#2
World!
Thread.sleep을 하면 코루틴이 아무 일을 하지 않는 동안에도 스레드를 양보하지 않고 독점합니다. ( 양보하지 않음 . )
한번에 여러 launch
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
println("launch1: ${Thread.currentThread().name}")
delay(1000L)
println("3!")
}
launch {
println("launch2: ${Thread.currentThread().name}")
println("1!")
}
println("runBlocking: ${Thread.currentThread().name}")
delay(500L)
println("2!")
}
실행결과
runBlocking: main @coroutine#1
launch1: main @coroutine#2
launch2: main @coroutine#3
1!
2!
3!
suspend된 이후 깨어나는 순서에 따라 출력 결과가 달라집니다.
상위 코루틴은 하위 코루틴을 끝까지 책임진다.
runBlocking 안에 두 launch가 속해 있는데 계층화되어 있어 구조적입니다.
runBlocking은 그 속에 포함된 launch가 다 끝나기 전까지 종료되지 않습니다.
import kotlinx.coroutines.*
fun main() {
runBlocking {
launch {
println("launch1: ${Thread.currentThread().name}")
delay(1000L)
println("3!")
}
launch {
println("launch2: ${Thread.currentThread().name}")
println("1!")
}
println("runBlocking: ${Thread.currentThread().name}")
delay(500L)
println("2!")
}
print("4!")
}
실행결과
runBlocking: main @coroutine#1
launch1: main @coroutine#2
launch2: main @coroutine#3
1!
2!
3!
4!
runBlocking 이 모두 끈나야 그이후 코드가 실행된다. ( runBlocking 은 안의 launch 가 모두 끈나기를 기다린다. )
- 계층적이다.
- 구조적이다.
- 부모를 캔슬시키면, 자식도 캔슬이 된다.
- 자식이 끈날때까지 부모가 기다린다 ( 관리하기 유용함 )
suspend 함수 ( 중단가능한 함수 )
delay, launch 등 지금까지 봤던 함수들은 코루틴 내에서만 호출 할 수 있습니다.
그럼 이 함수들을 포함한 코드들을 어떻게 함수로 분리할 수 있을까요?
코드의 일부를 함수로 분리할 때는 함수의 앞에 suspend 키워드를 붙이면 됩니다
import kotlinx.coroutines.*
suspend fun doThree() {
println("launch1: ${Thread.currentThread().name}")
delay(1000L)
println("3!")
}
suspend fun doOne() {
println("launch1: ${Thread.currentThread().name}")
println("1!")
}
suspend fun doTwo() {
println("runBlocking: ${Thread.currentThread().name}")
delay(500L)
println("2!")
}
fun main() = runBlocking {
launch {
doThree()
}
launch {
doOne()
}
doTwo()
}
실행결과
runBlocking: main @coroutine#1
launch1: main @coroutine#2
launch1: main @coroutine#3
1!
2!
3!
doOne은 delay와 같은 함수(suspend인 함수)를 호출하지 않았기 때문에
suspend를 붙이지 않은 일반 함수로 해도 됩니다.
만약 suspend 함수를 다른 함수에서 호출하려면 그 함수가 suspend 함수이거나
코루틴 빌더를 통해 코루틴을 만들어야 합니다.
* 반환값이 없는 코드를 만든 것이다 . ( 반환값이 있는 경우 ) runBlocking<Unit> 형태로 사용
* suspend를 사용하지 않고 delay 사용시 예외
Suspend function 'delay' should be called only from a coroutine or another suspend function
* 코드를 밖으로 빼고 suspend fun을 사용하여 핵심코드의 양을 줄이는 것이 좋다.
1-2 잡,구조화된동시성(1)
1-2 잡,구조화된동시성(2)
1-3 취소와타입아웃(1)
1-3 취소와타입아웃(2)
1-4 서스펜딩함수
1-5 코루틴컨텍스트와디스패처(1)
1-5 코루틴컨텍스트와디스패처(2)
1-6 CEH와슈퍼바이저잡
1-7 공유객체,Mutex,Actor(1)
1-7 공유객체,Mutex,Actor(2)
https://dalinaum.github.io/coroutines-example/
참고 : 정리가 잘되있어요 ~!
https://whyprogrammer.tistory.com/596
https://huisam.tistory.com/entry/coroutine1
'Android 공부 > Coroutine' 카테고리의 다른 글
[XX캠퍼스] 03. Kotlin Coroutines & Flow ( 취소와 타임아웃 ) (0) | 2022.07.21 |
---|---|
[XX캠퍼스] 02. Kotlin Coroutines & Flow ( 잡,구조화된동시성 ) (0) | 2022.07.20 |
코루틴 Flow 참고 블로그 주소 (0) | 2022.07.16 |
코루틴 기초 정리 _ part 02 Cancellation And Timeouts (0) | 2022.07.16 |
코루틴 기초 정리 _ part 01 (0) | 2022.07.14 |