본문 바로가기
언어/Kotlin

[Kotlin 문법 종합] - 쓰레드, 코루틴 (# 추가하기)

by 젼젼39 2024. 3. 6.

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를 사용하게 하는 게 목적.
        - 이전 프로세스의 상태(문맥)을 보관, 새로운 프로세스의 상태를 적재하는 작업 

        - 스레드와 코루틴 둘 다 동시성 프로그래밍을 위한 기술임