코루틴과 동시성 프로그래밍

hyeon·2022년 9월 24일
0

코틀린 스터디

목록 보기
4/5

여러개의 실행 루틴을 동작시키는 멀티스레드 대신 좀 더 다루기 쉬운 코루틴을 사용한다.

11-1 동시성 프로그래밍

=============

동기적

한개의 루틴을 완료한 후 다른 루틴을 실행하는 방식

비동기적

여러개의 루틴이 선행 작업의 순서나 완료여부와 상관없이 실행되는 방식


코루틴

co(함께 동시에) + routine (하나의 개별적인 작업)
넌블로킹 또는 비동기 코드를 동기코드처럼 쉽게 작성하면서도 비동기 효과를 낼 수 있다.

[용어 알아보기]

블로킹

  • 과정이 수행될 때까지 A의 코드가 진행되지 않고 내부 메모리 영역에서 작업이 마무리 될때까지 코드가 멈추게됨 => 블로킹하고 있다.

넌블로킹

  • EAGAIN과 같은 시그널을 A가 받아서 실행을 재개 할 수 있음
  • 완료 시그널을 받을 후 콜백 루틴등을 호출해 완료된 이후의 일을 처리 할 수 있음
    => 좀더 좋은 성능
  • 위 그림에서는 동시 수행처럼 보이지만 프로세서 코어수에 따라 동시에 수행될 수도 있고 두개의 태스크를 자주 교환해 동시에 수행 되는것 처럼 보이게 할 수 있다 (병행수행)

프로세스

프로세스는 실행중인 프로그램 (os로 부터 필요한 자원을 받아 프로세스가 됨) , 실제로 작업을 수행하는 것이 스레드(프로세스 내에서 stack만 할당받고 코드 데이터 힙은 공유)

  • 하나의 프로그램이 실행되면 프로세스가 시작된다.
  • 스레드에 비해 많은 비용이든다.

스레드

  • 자신의 스택만 독립적으로 가지고 나머지는 대부분 스레드끼리공유해 비용이 낮다.
  • 멀티 스레드를 구현하면 코드가 복잡해진다.

=> 코루틴은 문맥교환이 없고 최적화된 비동기 함수를 통해 비선점형으로 작동하는 특징이 있어 협력형 멀티태스킹을 구현할 수 있게 해준다.

+ 문맥교환: (하나의 태스크가 CPU를 사용하고 있는 상태에서 다른 태스크가 CPU를 사용하도록 하기위해 이전의 프로세스의 상태를 보관하고 새로운 프로세스의 상태를 적재하는 과정)
협력형 멀티태스킹 :(태스크들이 자발적으로 양보하며 실행을 바꿀수있는개념)

11-2 코루틴의 개념과 사용방법

코루틴은 비용이 많이 드는 문맥교환 없이 해당 루틴을 **일시 중단**해서 이러한 비용을 줄일 수 있다.
=> 운영체제가 스케줄링에 개입하는 과정이 필요없다.
+ 일시중단을 사용자가 제어할 수 있다.

코루틴의 주요 패키지

kotlinx.coroutines.* 로 접근한다.

코루틴 기본 예제

  • launch를 통해 코틀린 블록을 만들어냄
  • Hello 부분은 메인 스레드에 의해 바로 출력된다.
  • World! 부분은 코루틴부분으로 메인스레드와 분리되어 1초 뒤에 실행된다. (넌블로킹 코드)
  • suspend가 꼭 있으야 코루틴 기능을 사용할 수 있음 (코루틴 블록에서만 사용가능) suspend로 일시 중단 될 수 있다.
  • 지연함수는 또 다른 지연 함수 내에서 사용하거나 코루틴 블록에서만 사용해야한다.

launch

  • launch를 통해 코루틴 블록을 만들어내는것을 빌더의 생성이라고 한다.
  • job객체를 반환한다. (job: 백그라운드에서 실행되는 작업을 가리킴)
fun main() {
	val job = GlobalScope.launch { // Job 객체의 반환 
    delay(1000L) println("World!") 
    } 
    println("Hello,") 
    println("job.isActive: ${job.isActive}, completed: ${job.isCompleted}") //코루틴의 상태 반환
    Thread.sleep(2000L) println("job.isActive: ${job.isActive}, completed: ${job.isCompleted}")
}

코루틴의 생명주기는 프로그램의 생명주기에 의존된다.

suspend fun doWork1( ): String { 
	delay(1000) return "Work1" 
    } 
suspend fun doWork2( ): String { 
	delay(3000) return "Work2" 
    } 
private fun worksInSerial() { // 순차적 실행 		
	GlobalScope.launch { 
          val one = doWork1( ) 
          val two = doWork2( ) 
          println("Kotlin One : $one") 	
          println("Kotlin Two : $two") 
        } 
  	} 
fun main() { 
	worksInSerial( ) 
    readLine( ) // main()이 먼저 종료되는 것을 방지하기 
    //실행 결과 Kotlin One : Work1 Kotlin Two : Work2  
    }

launch에 정의된 doWork1()과 doWork2() 함수는 순차적으로 표현 할 수 있다. 내부적으로 비동기 코드로서 동작 할 수 있지만 코드만 봤을때 순차적으로 실행되는 것처럼 표현함으로서 프로그래밍의 복잡도를 낮춘다.

async

코루틴 빌더를 생성할 수있는 또하나의 키워드

  • lanch와 다른점
    Deferred를 통해 결과 값을 반환한다.
    지연된 결괏값을 받기 위해 await()를 사용한다.
    // 위의 코드 아래에 써져있다고 가정
    
    private fun worksInParallel() { 
    // Deferred<T>를 통해 결괏값을 반환 
    val one = GlobalScope.async {
    	doWork1( ) 
    } 
    val two = GlobalScope.async { 
    	doWork2( ) 
    } 
    GlobalScope.launch { 
    val combined = one.await( ) + "_" + two.await()
    println("Kotlin Combined : $combined") 
    	} 
    } 
    ... 
    workInParellel( )
    
    //실행 결과 Kotlin Combined : Work1_Work2
    • doWork1()과 2는 async에 의해 감싸져 있으므로 완전히 병행 수행 할 수 있다.

    • 이 코드에서는 1초 더 지연시킨 doWork1이 먼저 종료될것이라고 예상 가능하다.

    • 하지만 복잡한 루틴을 작성하는 경우에는 많은 태스크들과 같이 병행 수행되므로 어떤 루틴이 먼저 종료될지 알기 어려움
      => await를 사용해 태스크가 종료되는 시점을 기다렸다가 결과를 받도록한다. 그러면 현재 스레드의 블로킹 없이 먼저 종료되면 결과를 가져올 수 있다.

      	-사용 예시 : 안드로이드 UI스레드에서 블로킹 가능성이 있는 코드를 사용하면 앱이 중단되거나 멈출수 있음
      		이경우 await를 사용하면 ui를 제외한 루틴만 블로킹 되므로 ui멈춤을 해결할 수 있다.

코루틴의 문맥

코루린의 문맥은 CorutineContext에 의해 정의된다.
1. launch {..} 와 같이 인자가 없는 경우 CoroutineScope에서 상위의 context가 상속되어 결정되고
2. launch(Dispatchers.Default){..}와 같이 사용되면 GlobalScope에서 실행되는 context와 동일하게 사용된다.
코루틴이 사용할 스레드의 공동 pool을 사용한다. 이미 초기화 되어있는 스레드중에서 택하기 때문에 스레드를 생성하는 오버헤드가 없어 빠르다

시작지점 정하기

val job=async(start=CoroutineStart.LAZY){doWork1()}

-> start()await()가 호출될때 실제 루틴이 시작됨

runBlocking

새로운 코루틴을 실행하고 완료되기 까지 현재 스레드를 블로킹한다.

앞에서는 메인 스레드가 종료되는것을 막기위해 readLine()을 했지만 
메인 스레드 자체를 잡아두기 위해 main 자체를 블로킹 모드에서 실행 할 수있다.

fun main() = runBlocking<Unit> { // main() 함수가 코루틴 환경에서 실행
launch { // 백그라운드로 코루틴 실행
	delay(1000L) println("World!") 
}
  println("Hello") // 즉시 이어서 실행됨 
  // delay(2000L) 
  // delay( ) 함수를 사용하지 않아도 main() 함수내부의 코루틴이 모두 작동할 때까지 블로킹 
}

작업 완료 기다리기

Job 객체의 join함수를 사용한다. job에서 지정한 작업이 완료될때까지 기다린다.

코루틴과 시퀀스

sequence() : 표준라이브러리중 하나. 아주 많은 값을 만들어 내는 코드로 부터 특정 값의 범위를 가져올 수 있다.
sequence() 내부에 지연함수를 사용해 코루틴과 함께 최종 형태를 나중에 결정할 수 있는 lazy 시퀀스를 만들 수 있다.
무한 스크롤링을 구현하는 ui에 적용할 목록을 가져올 때 이용할 수 있다~

profile
남기고 싶은 개발자입니다 :>

0개의 댓글