1. 쓰레드
: 로직을 동시에 실행할 수 있게 함 // thread 키워드로 생성
- 작업 하나하나의 단위 : Thread (각각 독립적인 스택메모리영역 가짐)
- 하나의 메인 쓰레드가 존재 = 메인함수 = 실행흐름 //최소 1개의 메인 쓰레드는 존재해야
- 동시성 보장 수단 : Context Switching (운영체제 커널에 의한)
* Blocking : 스레드 A가 스레드 B의 결과를 기다리고 있을 때, A는 블로킹 상태.
- A는 B의 결과가 나오기 전에는 해당 자원을 사용하지 못함
- 스레드 A가 task 1을 수행하는 동안 task 2의 결과가 필요 -> 스레드 B를 호출함
- 스레드 B 호출-> (1) 스레드 A는 블로킹됨
(2) 스레드 B로 프로세스 간 스위칭 발생
-> task 2 수행
- task 3, 4는 A, B 작업이 진행되는 도중 멈추지 않고 각각 동시에 실행됨 ->??@@ 코루틴했을 때?? CPU 경쟁한다며?
//@@@@ 함수 호출하거나 기타등등 중첩될 때 스택 쌓이는 거랑 비슷한건가??
=> 운영체제 입장에서는 각 task를 쪼개서 수행해야 하니까 어떤 스레드를 먼저 실행할지 결정해야 함 => 스케줄링
- 메인 쓰레드 위에서 로직을 실행하는 게 아닌, 별도의 자식 쓰레드를 생성하면 동시에 로직 실행 가능
*프로세스 : 더블클릭으로 프로그램이 메모리에 올라가 실행될 때 생김
*쓰레드 : 프로세스보다 작은 단위, 생성되어 수행될 때 각각 독립된 stack 메모리 영역을 가짐
// 스택 영역에 여러개의 쓰레드가 생김. 동시에 진행됨
fun main() { //스레드 1
thread(start = true) { //스레드 2
for(i in 1..10) { ////import할 때 코드 긴거 누르기... concurrent.thread
println("Thread1: 현재 숫자는 ${i}")
runBlocking { //1초마다 쉰다
launch {
delay(1000) //import 할 때 Long 고르기
}
}
}
}
thread(start = true) { //스레드 3
for(i in 50..60) {
println("Thread2: 현재 숫자는 ${i}")
runBlocking {
launch {
delay(1000)
}
}
}
}
}
//위의 스레드 2개는 서로 cpu 자원을 두고 경쟁하게 됨. 번갈아가면서가 아니라 섞임...
@@그럼 위의 코드는 코루틴인거지...? 스레드를 사용하는?
@Volatile 키워드...??/
ㅇㅇ
2. 코루틴
- 나머지 코드와 동시에 작동하는 코드 블록을 실행해야 한다는 점에서 스레드와 비슷
- but 특정 스레드에 바인딩되지 않아서 한 스레드에서 실행을 중지하고 다른 스레드에서 실행을 재개할 수 있음
- 로직들을 협동해서 실행하자는 게 목표
(+) 최적화된 비동기 함수 사용
(+) 하드웨어 차원의 효율적인 할당을 가능하게 함
(+) 안정적인 동시성, 비동기 프로그래밍을 가능하게 함
(+) 쓰레드보다 더 가볍게 사용 가능
- 작업 하나하나의 단위 : Coroutine Object
- 여러 작업 각각에 Object를 할당 (객체니까 heap에 적재됨 - 코틀린 기준)
- 동시성 보장 수단 : Programmer Switching (No-Context Switching)
: 소스코드를 통해 switching 시점을 정하게 됨. (운영체제는 관여하지 않음)
* Suspending (Non-Blocking)
: Object 1이 Object 2의 결과를 기다릴 때 Object 1의 상태는 suspend로 바뀜
- but Object 1을 수행하던 스레드는 그대로 유효 -> Object 2도 Object 1과 동일한 스레드에서 실행됨
(스레드 레벨이 아니라 코루틴 레벨이라서 ?? @@stack이 아니라 heap에 있어서 영향받지 않는건가? 그냥 참조하는 거랑 비슷하다고 보면 되나??@@)
- task 1 수행 중 task 2의 수행요청 발생 -> 컨텍스트 스위칭(문맥 보관, 새로 적재)없이 동일한 스레드 A에서 수행 가능
- 스레드 C처럼 하나의 스레드에서 여러 task Object들을 동시에 수행 가능
=> 코루틴 = Light-Weight Thread
- 스레드(동시처리를 위해 스택영역을 별도로 할당)처럼 동작하지 않음
- but 동시성 보장
- 하나의 스레드에서 다수의 코루틴 수행 가능
- 하나의스레드를 더욱 잘개 쪼개 사용하는 것
* 코틀린에서 코루틴을 사용하기 위해서는 외부 종속성을 추가해야 함 (외부 라이브러리, 모듈)
// https://github.com/Kotlin/kotlinx.coroutines#android Gradle 의 아래 코드를
Gradle Scripts의 build.gradle(Module : app) 의 implementation 적당한 곳에 붙여넣고 sync now 누르기
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
1) 코루틴 빌더의 종류
(1) launch 빌더 : 결과값이 없는 코루틴 빌더
- job 객체로 코루틴을 관리
(Job 객체 : 다양한 함수 가짐... join : 현재 코루틴이 종료되는 것 기다림 / cancel : 현재의 코루틴을 즉시 종료 )
(2) async 빌더 : 결과값이 있는 코루틴 빌더.
- Deffered 타입으로 값을 리턴함
2) 스코프로 범위 지정 가능
(1) GlobalScope : 앱이 실행된 이후에 계속 수행되어야 할 때 사용
(2) CoroutineScope : 필요할 때만 생성, 사용 후 정리해야함
3) 실행할 쓰레드를 Dispatcher로 지정 가능
: 하나의 쓰레드 안에서 여러개의 코루틴을 나눠 쓰는거라 메인 쓰레드를 골라야 실행 가능
(1) Dispatchers.Main : UI와 상호작용하기 위한 메인 쓰레드. = 메인 쓰레드 안에서 여러개의 루틴들을 나눠 쓰겠다
(2) Dispatchers.IO : 네트워크나 디스크 I/O작업에 최적화되어있는 쓰레드
- 외부로부터 파일이나 데이터베이스 등에 작업하기 위한...
(3) Dispatchers.Default : 기본적으로 CPU 최적화되어있는 쓰레드
* 안드로이드에서는 특히 Dispatcher 간의 변환을 해야 하는 작업 고려해야 함
--> ex. UI가 적용되지 않는 로직들을 동시적으로 실행하다가 결과적으로 UI에 결과값을 갱신해야 할 때...
예시 1) 현 실습환경인 JVM은 실행 후 종료되는 거라서, 항상 앱이 켜져있는 안드로이드와는 다름...
--> 실행하면 main이 먼저 종료되어 코루틴의 결과를 알 수 없게됨
fun main(args: Array<String>) {
println("메인쓰레드 시작")
var job = GlobalScope.launch { //비동기적으로 동작하긴 하는데
delay(3000) //main이 먼저 끝나서 출력이 안됨
println("여기는 코루틴...")
}
println("메인쓰레드 종료")
}
//메인쓰레드 시작
//메인쓰레드 종료
예시 2) 비동기적으로 코루틴 결과를 확인하려면 job의 join 메소드 사용하기
fun main(args: Array<String>) {
println("메인쓰레드 시작")
var job = GlobalScope.launch { //job이 할당됨
delay(3000)
println("여기는 코루틴...")
}
runBlocking { //job이 끝날 때 까지 기다리겠다고 명시
job.join() //안드로이드 앱에서라면 이게 필요 없음
}
println("메인쓰레드 종료")
}
예시 3) 다른 코루틴 스코프 사용해도 됨
fun main(args: Array<String>) {
println("메인쓰레드 시작")
var job = CoroutineScope(Dispatchers.Default).launch { //여기 다름
delay(3000)
println("여기는 코루틴...")
}
runBlocking {
job.join()
}
println("메인쓰레드 종료")
job.cancel() //CoroutineScope는 임시라서 할당 취소해주기
}
예시 4) 여러 개의 코루틴 사용 가능
- 코루틴의 결과값을 리턴받을 수 있음
//코루틴의 결과값을 리턴받아야 하므로 await은 일시주우단이 가능한 코루틴에서 실행가능함
println("메인쓰레드 시작")
var job = CoroutineScope(Dispatchers.Default).launch {
var fileDownloadCoroutine = async(Dispatchers.IO) {
delay(10000)
"파일 다운로드 완료"
}
var databaseConnectCoroutine = async(Dispatchers.IO) {
delay(5000)
"데이터베이스 연결 완료"
}
println("${fileDownloadCoroutine.await()}")
println("${databaseConnectCoroutine.await()}")
}
runBlocking {
job.join() //job이 끝날 때 까지 main 스레드는 기다림
}
println("메인쓰레드 종료")
job.cancel()
//메인쓰레드 시작
//파일 다운로드 완료
//데이터베이스 연결 완료
//메인쓰레드 종료
// https://kotlinlang.org/docs/coroutines-basics.html#your-first-coroutine 다시 읽어보기
fun main() = runBlocking { //this: CoroutineScope
launch { // 새로운 coroutine launch 후 계속
delay(1000L) //1초 딜레이
println("World") // 딜레이 후 출력됨
}
println("Hello") //먼저 실행됨
}
3. 쓰레드와 코루틴
1) 동시성 프로그래밍 : 컨텍스트 스위칭(문맥 교환)이 중요
* 컨텍스트 스위칭 : 하나의 프로세스가 cpu를 사용중일 때 다른 프로세스가 cpu를 사용하게 하는 게 목적.
- 이전 프로세스의 상태(문맥)을 보관, 새로운 프로세스의 상태를 적재하는 작업
- 스레드와 코루틴 둘 다 동시성 프로그래밍을 위한 기술임
'언어 > Kotlin' 카테고리의 다른 글
[Kotlin 공부] 시간 출력, 비교하기 (0) | 2024.03.13 |
---|---|
[Kotlin 문법 종합] 스레드 활용 실습 (@ 수정하기) (0) | 2024.03.08 |
[Kotlin 문법 종합] - 확장함수, 비동기 프로그래밍 @@@수정하기 (2) | 2024.03.06 |
[Kotlin 문법 종합] - 여러 인스턴스 리턴, 자신의 객체 전달 @@수정하기 (0) | 2024.03.06 |
[Kotlin 문법 종합] - 자료형 변환, 타입확인 @@@ 수정하기 (0) | 2024.03.06 |