ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Kotlin Coroutine 사용하기
    Kotlin 2021. 5. 13. 14:26
    반응형

     

    코루틴은 범용적인 개념이다. 코루틴와 같이 코씨(?) 라서 뭔가 코틀린에서 파생된것처럼 보이겠지만 전혀 무관한다.

    코틀린에서 코루틴을 사용하기 위해선 일단 아래와같이 라이브러리를 세팅부터 해줘야한다. 별다른 설정이 필요없이 추가만 해주면 사용하는데 무리가 없어 보인다. 

    사용하기 위한 설정 

    kts 기반으로 만든 gradle 설정 파일을 사용중이라 아래와 같이 표현했지만 , 환경에 맞게 사용하면 될거같다. maven 사용시에도 크게 사용에 무리는 없어 보인다. 설정시 자신이 사용중인 코틀린버전과 맞추주는 것이 중요하다.

    implementation( "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3")

    참조 :  github.com/Kotlin/kotlinx.coroutines 

    suspend

    코틀린이 제공하는 비동기 기본요소는 suspend를 사용하여 간단하게 사용이 가능하다. 일단 코루틴은 경량 쓰레드 라고 생각하면된다. 실제 일반적인 쓰레드 를 수십개 사용한것과 코루틴 수십개를 사용한경우 cpu, mem 등 대부분 자원소모에서 압도적인이고 아름다운 사용량을 보여 준다. 개인적인 생각이지만 자바에서 쓰레드를 사용한다는것은 굉장이 헤비한 작업이라고 생각한다. 그렇지 않았다면 쓰레드풀같은것들을 사용해가며 사용했을까 ? 라는 생각이 든다. 

    코루틴은 다시 시작하거나 다른스레드가 처리가 가능 하며 여러가지 로직들을 동작 시점을 제어가능 하도록 해준다

    suspend fun delay( time : Long ) {  .... }

    해당 함수는 시작과 동시에 정지도 가능하며 특정조건에 해당될경우 다시 시작하게 할수 있다. 잘보면 별다른 반환 유형이 없다 . 자바로 치면 Future 같이 래핑이 가능한게 필요 할거 같은데 명시되있지 않다. 실제로 webflux 를 사용할떄도 비동기 처리시 future , mono 로 꼭 래퍼를 사용하여 반환처리를 했던거 같은데 딱히 그런게 보이지 않는다. 

     suspend 를 사용할경우 일반 호출과 동일하게 작동 하지만 반환값을 얻고 나머지 코드를 실행 하려면 호출이 완료되는 시점을 기달려야 한다. 

    suspend fun wait(): { ... }
    
    suspend fun somThing() : Unit {
    
             delay(1000L)
    
             println("waiting")
    
            val result = wait()
    
            println( "wait end.")
    
    }

     일반 함수에서는 suspend 함수를 직접 호출할 경우 컴파일이 안된다고 한다. 실제로도 일반 함수나 클래스에서 해당 suspend 함수를 호출할경우 일반 형태에서는 호출이 안된다고 IDE 레벨에서 에러가 나온다.

    결론적으로 suspend 를 사용하기 위해선 코루틴을 이용하여 제어 해야하거나 무조건 전부 suspend 로 선언해서 사용 해야 한다는 결론이 나온다.  

    Coroutine Builders 

    코루틴 빌더 는 코루틴을 간단하게 작성 가능하게 해주는 함수 이다. 

    일반적으로 예제에 많이 나오는것들은  runBlocking , aync , launch 세가지 인거 같다. 블로그들이나 책을 보아도 이 세가지를 핵심적으로 다룬다. 

    runBlocking : 현재 스레드 자체를 블럭 , 선언될경우 자식이 실행이 모두 끝날때까지 모두 차단, 일시중단 함수 

    launch : 특정 시점을 백그라운드로 실행하고 다음단계로 넘어가게 한다. 

    async : 값을 반환하는 비동기 작업 처리. 자바의 Future 나 Javascript 의 Promise 와 동일 역할 . await 를 사용하여 결과 값이 나올때까지 대기 할수 있음. 단, await 는 내부적으로 suspening 함수 이기 떄문에 runblocking 을 활용하여 사용 함. 

    <runblocking 예제>

        println("너,")
            // 현재 실행중인 스레드를 해당 함수의 실행이 마무리 될떄까지 블락, suspend 함수를 호출할때 활용 가능
            runBlocking {
                // coroutine 함수 내부, 함수는 2초동안 정지상태로 유지됨.
                println(".....")
                delay(2000L)
            }
            // 아래 코드는 2초 뒤에 실행됨
            println("2초 늦음")

    <launch 예제>

    
         GlobalScope.launch { // 새로운 코루틴을 백그라운드에서 실행하고 다음으로 진행
            delay(1000L)
            println("기달려!!")
        }
        println("야!!")
        runBlocking {     // 현제 실행중인 스레드를 블락
            delay(2000L)  // 2초 가량 대기하면서 이전상태 유지
        } 
    
    
    
    

    <async 예제>

    val result: Deferred<String> = GlobalScope.async {
                delay(1000L)
                "Waitting!!!"
            }
    
            runBlocking {
                println("You ${result.await()}")
            }
    
    
    

     

    정리 해봤을때 코루틴을 사용한다는것은 비동기 처리를 하겠다는 의미이며 이때 runblocking 경우 모두 일시정지 상태를 만들기 때문에 의미에 부합하지 못하는것 같다. 그리고 코루틴 빌더중에 runblocking 을 제외한 나머지 함수들은 확장 함수가 존재하고 이것들이 확장된 이유는 코루틴 자체를 구조화하여 사용 하는것을 권장 하는 의미라고 한다.  코틀린 개발팀에서도 코루틴 내에서 현재 사용중인 쓰레드를 블락하지 않고 사용하는것을 의도 했다고 한다. 

    코루틴을 사용할때 구조화하여 사용할경우 좀더 내가 원하는 시점에 비동기로 처리하고 모든 결과가 나온 시점에 따른 흐름제어자체가 좀더 원활할수 있게 구성이 가능할것으로 생각이 되며, 개인적으로 코루틴을 사용할때는 무조건 구조화 해서 사용하는것이 좋다고 생각이 든다. 

    구조화할때 일반적인 가이드를 보면 크게 runblocking 으로 감싸서 코루틴을 생성하고 그안에서 launch , async 를 적절하게 사용하여 구성하는 방식을 가이드 하는것 같다. 

     	
        fun case1() = runBlocking {
            val deferredResult = async {
                delay(1000L)
                "호!"
            }
            println("무야${deferredResult.await()}")
        }
    
        
        fun case2() = runBlocking {
            launch {
                delay(1000L)
                println("호!")
            }
            println("무야")
        }
    
        
        fun case3() = runBlocking {
            delay(1000L)
            println("무야호!")
        }

    각 케이스별로 동일한 "무야호"가 출력 되게 된다. 하지만 각각의 진행박식은 다르다 케이스1 은 전체적으로 블럭킹 된게 아니라 비동기로 진행하지만 1초 딜레이를 주어서 "호!"가 뒤늦게 나오도록 하였다. 실제 출력할때도 await 를 사용하여 처리가 끝나길 기다렸다 마무리되면 출력하게 된다. 

    두번째 케이스는 기다림 없이 마지막 출력 코드가 먼저 출력이 되고 그다음 라인에서 launch 에 걸린 내용이 출력된다. 

    세번째 케이스는 전체가 대기를 하기 때문에 별다른 처리 없이 출력이 가능하다. 

     

    fun case4() = runBlocking {
            launch {
                delay(200L)
                println("야")
            }
    
            coroutineScope { //  coroutine scope 생성
                launch {
                    delay(500L)
                    println("호")
                }
    
                delay(100L)
                println("무")
            }
    
            println("!!!!!") 
        }

    위에 코드를 분석해보면 무야호!!!!! 가 순서대로 라인으로 출력되게 된다. 일단 launch 는 실행 시키고 바로 다음으로 넘어가게 된다. 이때 약간의 딜레이가 생기기 된다. 

    그다음 coroutine scope 가 실행되게 되는데 이녀석의 역할은 이 영역안에 있는것들이 완료 될때까지 코루틴을 일시 정지 시키게 된다. 만일 그렇지 않다면 바로 느낌표가 먼저 출력될것이다. 

    코루틴 스코프 안에 보면 안에서 다시 launch 를 선언 했는데 이때 딜레이 시간을 봤을때 가장 마지막에 실행 완료될 것이다. 그렇기 때문에 다음 로직에서 100L 을 기다린후 "무"가 먼저 출력되고 그다음 이미 앞에서 실행된 launch 에서 200L 이 지난시점에서 "야"가 실행 된다. 

    그리고 최종적으로 가장 딜레이가 컸던 "호"가 실행이 된다. 앞서 말했듯이 코루틴 스코프가 생길경우 이 안에 있는것들이 모두 실행 되야 다음으로 넘어가기 때문에 마지막에 "!!!!" 이 출력되게 된다. 

    여기서 중요한 포인트는 최초에 "야"를 출력하려는 로직이 runblocking 영역안에서 비동기적으로 백그라운드에 돌고 있다는것이다. 

    필요한 로직들중에 먼저 선행적으로 돌릴것들은 백그라운드로 돌리고 나머지 로직들의 특성에 맞게 스코프를 선언하여 처리하는 방식으로 조합을 한다면 많은 로직들의 순서를 제어하면서 사용이 가능 할것 같다. launch , async 를 적절히 사용하는 경험치를 쌓을 필요가 있어 보인다. 

        fun case5() = runBlocking {
    
            launch {
                delay(800L)
                println("!!!!!")
            }
            coroutineScope {
                val tmp = async {
                    delay(500L)
                    "무"
                }
                println( tmp.await() )
                println("야")
            }
            println("호")
        }

    async  를 사용한 샘플을 만들어 보았다. 여기서 의도한건 await 다음 "야"가 출력이 된다가이다. 최초 무-야-호-!!!!! 이순서대로 출력을 의도 하였다. 코루틴 스코프 안에서 바로 야가 출력되는게 아니라 무가 출력된이후에만 출력되는것이 핵심 의도이다. 

    이 각각의 시점 제어하는 곳에서 알맞는 로직을 배치하여 처리한다면 아주 우아한 코딩이 되지 않을까 싶다. 일단 이번에는 간단한 블럭단위의 코루틴 제어에 대해서만 정리 하였다. 

     코루틴 내부에는 channel , producer / consumer 등 여러가지 다른 사용방법들을 더 공부 해야한다. 그러기전에 이런 간단한 최소한의 블럭제어부터 정리하고 넘어 가고자 한다. 

    반응형

    'Kotlin' 카테고리의 다른 글

    코틀린 vs Java  (0) 2021.07.12
    Kotlin 기초 (2) - Collection & 표준함수  (0) 2021.03.18
    Kotlin 기초 문법 예제  (0) 2021.03.17
Designed by Tistory.