관리 메뉴

피터의 개발이야기

[kotlin] Kotlin Coroutine의 비동기 처리 장점, RxKotlin의 Callback 지옥 본문

Programming/Kotlin

[kotlin] Kotlin Coroutine의 비동기 처리 장점, RxKotlin의 Callback 지옥

기록하는 백앤드개발자 2024. 5. 27. 10:10
반응형

ㅁ 들어가며

ㅇ 코틀린 코루틴의 개념을 정리하였다.

ㅇ 최근 트랜드인 비동기처리에 있어서 코틀린 코루틴의 차이점에 대해 알아보았다.

 

ㅁ Coroutine이란?

Coroutines은 Co + Routines 약자로, Co 는 Cooperation을 의미하고, Routines는 하나의 테스크를 의미한다. 풀어서 말하자면, 서로 협력하는 함수를 의미한다. 

 

ㅁ 간결하고 읽기 쉬운 코드

ㅇ 코루틴을 사용하면 복잡한 비동기 작업을 마치 동기 코드처럼 작성할 수 있다. 

ㅇ 이는 콜백 지옥을 피하고 코드의 가독성을 크게 향상시킨다.

ㅇ 아래는 코틀린의 가독성을 확인하기 위해  Callback 지옥, RxKotlin, coroutine을 비교해 보았다.

 

Callback 지옥

fun goWork(person: Person) {
  val 잠든패카= person
  wakeUp(잠든패카){ 비몽사몽한패카 ->
    takeShower(비몽사몽한패카){ 깨끗한패카 ->
      putonShirt(깨끗한패카){ 옷입은패카 ->
        getOnBus(옷입은패카){ 버스를탄패카 ->
          finish(버스를탄용준){ 출근한패카 ->
            출근한패카.doWork()
          } // finish end
        } // getOnBus end
      }// putonShirt end
    }// takeShower end 
  } // wakeUp end
}

ㅇ Callback 함수의 기초는 람다 표현식이다.

   ㄴ 람다 표현식은 메소드의 매개변수로 전달될 수도 있으며, 메소드의 결과값으로 반환될 수 있다.

   ㄴ Java SE 8부터는 이러한 람다 표현식을 사용하여 자바에서도 함수형 프로그래밍을 할 수 있게 되었다.

   ㄴ 람다  방식을 이용해 하나의 테스크가 마치면 callback 형태로 구현하였다.

콜백으로 비동기처리를 구현하면, 흔히 이야기 하는 콜백 지옥에 빠질 수 있다.

ㅇ 각 콜백 지옥의 복잡함을 해소하기 위해  "// ~~ end"로 표시하였다.

 

RxKotlin

fun goWork(person: Person) {
  val 잠든패카= person
  
  Observable
    .just(person)
    .observeOn(MAIN_Thread)
    .subscribeOn(IO_Thread)
    .flatMap{ 잠든패카 -> wakeUp(잠든패카)}
    .flatMap{ 비몽사몽한패카 -> takeShower(비몽사몽한패카)}
    .flatMap{ 깨끗한패카 -> putOnshirt(깨끗한패카)}
    .flatMap{ 옷입은패카 -> getOnBus(옷입은패카)}
    .flatMap{ 버스를탄패카 -> finish(버스를탄패카)}
    .subscribet({ 출근한패카 -> 
      출근한패카.doWork()
	}, {
      실패했을때처리( )
	}
}

ㅇ RxKotlin는 옵저버 패턴 구조로 되어 있다. 

  ㄴ 옵저번 패턴이란,
    옵저버(관찰자)들이 관찰하고 있는 대상의 상태가 변경되면 각 관찰자들에게 알림을 받아 조치를 취하는 행동 패턴이다.
 

  ㄴ Pub/Sub(발행/구독) 모델로 알려져 있다.

ㅇ 콜백 코드에 비해 flatMap을 기준으로 이전 테스크의 결과를 받아 후속 테스크가 수행되기 때문에 보기에 편하다.

ㅇ 하지만, Rx의 캡슐화되고 함수화로 내제화된 코드를 배워야하는 러닝커버가 있다.

  ㄴ just, observerOn, subscribeOn의 개념뿐만 아니라 에러처리를 위한 개별 함수를 공부해야한다.

 ㄴ 내제화된 코드에서 수행할 때에 내 생각과 의도치 않게 움직이는 경우가 많았다.

 

Kotlin + coroutine

suspend goCompany(person: Person){
  val 잠든패카 = person

  try {
    val 비몽사몽한패카 = wakeUp(잠든패카)
    val 깨끗한패카 = takeShower(비몽사몽한패카)
    val 옷입은패카 = putonShtrt(깨끗한패카)
    val 버스를탄패카 = getonBus(옷입은패카)
    val 출근한패카 = finish(버스를탄패카)
    
    출근한패카.doWork()
  } catch (e: Exception){
    실패했을때처리( )
  }

ㅇ suspend를 사용하면 동기식 처럼 보이지만 비동기식으로 해석되어 실행되는 코드를 작성할 수 있다.

ㅇ 각 함수들의 작업은 언제 끝날지 모르지만 비동기 작업들의 순서는 정확히 지켜진다. 

ㅇ 이게 무슨 말인가? 동기랑 비동기랑 무슨 차이가 있지 모를 수 있다.

ㅇ 여기서 suspend에 대해 더 이해가 필요하다.

 

ㅁ Coroutines의 비동기처리 이해를 위한 suspend와 block 비교

coroutine의 비동기처리는 thread를 suspend(유예하다)하지 block(차단하다)하지 않는다.

 

block
ㅇ block은 thread를 점유하게 되어 thread는 다른 처리를 진행할 수 없다.

ㅇ 태스크가 특정 조건(예: I/O 완료, 자원 사용 가능 등)을 기다리고 있을 때 발생한다.

ㅇ 조건이 만족되면 태스크는 다시 실행 상태로 전환된다.

ㅇ 블로킹 호출은 주로 동기 작업에서 사용된다.

fun main(){
    println("Coroutine")
    Thread.sleep(3000L) // 3초 동안 thread를 점유한다.
    println("Hello")
}

결과: 

Coroutine
Hello

 

suspend

 suspend 함수는 일시 중단될 수 있는 함수이다.

ㅇ 코루틴은 suspend 함수를 호출할 때, 현재 코루틴을 중단하고 다른 코루틴을 실행할 수 있다.
ㅇ suspend 함수는 비동기 작업을 수행할 때 유용하다. 예를 들어, 네트워크 요청이나 파일 I/O 작업을 수행할 때 사용된다.
ㅇ suspend 상태에서는 코루틴이 CPU를 점유하지 않으며, 다른 코루틴이 실행될 수 있다.

import kotlinx.coroutines.*

fun main(): Unit = runBlocking {
    launch {
        delay(3000L) // 3초간 처리를 중단. 스레드가 다음 스팩을 수행하여 Hello가 표시된다.
        println("Coroutine")
    }
    println("Hello")
}

결과:

Hello
Coroutine

 

요약하자면,

suspend은 코루틴을 일시 중단하고, 다른 코루틴이 실행될 수 있게 한다. CPU를 점유하지 않으며, 비동기 작업에 적합하다.
block은 현재 스레드를 차단하고, 작업이 완료될 때까지 기다린다. CPU를 점유하며, 동기 작업에 적합하다.

 

ㅁ 함께 보면 좋은 사이트

쾌락코딩 - 코틀린 코루틴(coroutine) 개념 익히기

devkuma - Kotlin 코루틴(Coroutines)

 

반응형
Comments