코틀린

코루틴 Coroutines

정혜현 2024. 8. 2. 00:31

코루틴 Coroutines

많은 양을 동시 작업하며 메모리를 절약하여 비동기 프로그래밍에 권장되는 동시 실행 설계 패턴

지정된 작업 범위 내에서 실행이 되기 때문에 메모리 누수 방지

 

 

 

동기 Synchronous와 비동기 Asynchronous

동기 사전적 의미로 동시에 일어난다는 뜻이라 여러 일을 동시에 처리해준다는 의미같아서 비동기와 헷갈렸는데 일의 발생과 결과를 동시에 처리해준다고 이해하면 된다. 즉 일이 발생하면 그 일을 처리해줄 때까지 다른 일을 맡지 않는다.

따라서 동기는 직렬적이고 비동기는 병렬적이다. 비동기와 병렬을 비읍으로 묶는 연상기억법으로 장기기억을 강화했다. 

 

 

 

쓰레드Thread와 코루틴

  • 기능적으로는 비슷하지만 단일 쓰레드 내 여러 개의 코루틴을 실행. 코루틴은 특정 쓰레드에 바인딩되지 않고 한 쓰레드에서 일시중단하고 다른 쓰레드에서 다시시작 가능  
  • 쓰레드는 CPU가 쓰레드를 점유하면서 실행, 종료를 반복하며 메모리 소모하는 Context Switching이 발생하기 때문에 많은 양의 쓰레드를 갖기가 어렵지만 코루틴은 쓰레드가 아닌 루틴을 일시 중단suspend 하는 방식
  • 구조화된 동시성 : 코루틴이 수명을 한정하는 특정 CoroutineScope에서만 시작될 수 있다는 것을 의미, 코드의 모든 오류가 적절하게 보고되고 손실되지 않도록 보장
  • 쓰레드보다 리소스를 적게 사용하므로 사용 가능한 메모리를 고갈시키는 코드는 코루틴으로 표현

 

 

fun main() = runBlocking { //코루틴스코프
    launch { //새로운 코루틴 시작 및 지속
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello") // main coroutine continues while a previous one is delayed
}

 

 

 

 

 

 

코루틴 사용

Context로 Scope를 만들고 Builder를 이용하여 코루틴을 실행

1. Dispatchers : 어떤 쓰레드에서 실행할 것인가
2. Scope : 실행될 범위
3. launch 또는 async로 코루틴 실행 

 

 

 

 

 

 

 

CoroutineScope

코루틴이 실행되는 범위

 

CoroutineScope : CoroutineScope(CoroutineContext) 사용자 지정 

// 메인 쓰레드에서 실행될 사용자 정의 Scope
val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
    // 메인 쓰레드 작업
}
// 백그라운드에서 실행될 사용자 정의 Scope
CoroutineScope(Dispatchers.IO).launch {
    // 백그라운드 작업
}

 

GlobalScope : 앱이 실행될 때부터 종료될 때까지 실행

// 앱의 라이프사이클동안 실행될 Scope
GlobalScope.launch {
    // 백그라운드로 전환하여 작업
    launch(Dispatchers.IO) {
    }
    // 메인쓰레드로 전환하여 작업
    launch(Dispatchers.Main) {
    }
}


 ViewModelScope : ViewModel 제거 시 자동취소

class MyViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // ViewModel이 제거되면 코루틴도 자동으로 취소됩니다.
        }
    }
}


LifecycleScope : Lifecycle 객체 대상(Activity, Fragment, Service...)의 Lifecycle 끝날 때 자동취소

class MyActivity : AppCompatActivity() {
    init {
        lifecycleScope.launch {
            // Lifecycle이 끝날 때 코루틴 작업이 자동으로 취소됩니다.
        }
   }
}


liveData : LiveData가 활성화되면 시작, 비활성화되면 자동취소

val user: LiveData<User> = liveData {
    val data = repository.loadUser() // suspend function
    emit(data)
}

 

 

 

 

 


CoroutineContext

어떤 쓰레드에서 실행할 것인지에 대한 동작을 정의하고 제어하는 요소

Job : 코루틴을 고유하게 식별하고 제어

val job = CoroutineScope(Dispatchers.IO).launch {
    // 비동기 작업
}
job.join()      // 작업이 완료되기까지 대기
job.cancel()    // 작업 취소
val job1 = Job()
CoroutineScope(job1 + Dispatchers.Main).launch {
    // 메인 쓰레드 작업
    launch(Dispatchers.IO) {
        // 비동기 작업
    }
    withContext(Dispatchers.Default) {
        // 비동기 작업
    }
}
val job2 = CoroutineScope(Dispatchers.IO).launch {
    // 비동기 작업
}
job1.cancel() // job1이 연결된 코루틴 작업 취소


Dispatchers : 코루틴을 어떤 쓰레드에서 실행할 것인지 동작 지정

 

Dispatchers.Main : 안드로이드의 메인 쓰레드로 UI 작업용
UI를 구성하거나 LiveData를 업데이트 할 때 사용

Dispatchers.IO : 네트워크, 디스크 I/O용
Retrofit으로 네트워크 통신을 하거나 File이나 Room 데이터베이스에서 데이터를 읽고 쓸 때 사용

Dispatchers.Default : CPU 사용량이 많은 무거운 작업용
데이터를 가공하거나 복잡한 연산, JSON 파싱 할 때 사용

 

 

 

 


CoroutineBuilder

CoroutineScope와 CoroutineContext로 코루틴을 실행시켜주는 함수

launch : Job 객체, 결과값을 반환하지 않으므로 실행 후 결과값이 필요 없는 작업에 사용

CoroutineScope(Dispatchers.Main).launch {
    // 결과값이 필요없는 작업
}


async : Deferred 객체, await() 함수를 사용하여 코루틴 작업의 최종 결과값 반환 

val deferred = CoroutineScope(Dispatchers.Main).async {
    // 결과값
    "Hello Coroutine!"
}
val message = deferred.await() // await()함수로 결과값 반환
println(message)


withContext : async와 동일하게 결과값 반환하며 await()을 호출할 필요가 없다.
코루틴 내부나 susfend 함수 안에서 구현 가능하며 콜백이 필요 없이 코드의 쓰레드 풀을 제어할 수 있기 때문에 네트워크 요청이나 DB 조회 같은 작업에 주로 사용

init {
    viewModelScope.launch {       // Dispatchers.Main
        val user = getUserInfo()  // Dispatchers.Main
    }
}
suspend fun getUserInfo(): User =                   // Dispatchers.Main
        withContext(Dispatchers.IO) {               // Dispatchers.IO
            val response = apiService.getUserInfo() // Dispatchers.IO
            if (response.isSuccessful) {            // Dispatchers.IO
                return@withContext response.body()  // Dispatchers.IO
            } else {                                // Dispatchers.IO
                return@withContext null             // Dispatchers.IO
            }                                       // Dispatchers.IO
        }

 

 

 

 

 

susfend function

반드시 코루틴 안에서만 호출 가능코루틴 전용 메소드
코루틴이 일시중단 susfend 되거나 다시 재개 resume 될 수 있기 때문에 컴파일러에게 코루틴 안에서 실행할 메소드임을 정의하기 위해 메소드명 앞에 suspend를 붙인다.

 

fun main() = runBlocking { //코루틴스코프
    launch { //새로운 코루틴 시작 및 지속
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello") // main coroutine continues while a previous one is delayed
}

 

launch : 코루틴 빌더. 다른 코드와 동시에 작업을 코루틴에서 시작한다. 스코프 내의 지연시킨 "World!" 프린트 작업은 독립적으로 이루어져 "Hello"가 먼저 인쇄된다.

delay : 특정 시간 동안 코루틴을 일시 중단하는 함수로 코루틴을 일시 중단해도 기본 스레드는 차단 되지 않지만 다른 코루틴이 실행되고 코드에 기본 스레드를 사용할 수 있다.

runBlocking : fun main()와 중괄호 안에 코루틴이 있는 코드를 연결하는 코루틴 빌더. IDE에서 CoroutineScope바로 뒤에 있는 힌트로 강조 표시. runBlocking이 코드에서 제거하거나 잊어버리면 CoroutineScope 에서만 선언

 

 

runBlocking 과 coroutineScope빌더

본문과 모든 자식이 완료될 때까지 기다리는 점은 비슷하나 runBlocking 메서드는 현재 스레드를 대기 상태로 차단하는 반면 coroutineScope는 다른 용도로 기본 스레드를 해제하여 일시 중단한다는 차이가 있다. 이러한 차이로 runBlocking 은 일반 함수이고 coroutineScope는 suspend 함수이다.

// doWorld를 순차적으로 실행한 후 "Done"을 실행합니다.
fun main() = runBlocking {
    doWorld()
    println("Done")
}
​
// 두 섹션을 동시에 실행합니다.
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
    launch {
        delay(2000L)
        println("World 2")
    }
    launch {
        delay(1000L)
        println("World 1")
    }
    println("Hello")
}

//Hello
//World 1
//World 2
//Done

 

 

 

 

 

ᖜ ‿ᖜ 도움받은 곳

https://0391kjy.tistory.com/49