Kotlin_08

김재현·2023년 4월 18일
0

1. 코루틴(Coroutine)

  • 넌블로킹 또는 비동기 코드를 마치 동기 코드처럼 쉽게 작성하면서도 비동기 효과를 낼 수 있다.
  • 문맥 교환이 없고 최적화된 비동기 함수를 통해 비선점형으로 작동하는 특징이 있어 협력형 멀티태스킹을 구현할 수 있게 해준다.
  • 비용이 많은 문맥 교환 없이 해당 루틴을 일시 중단해서 이러한 비용을 줄일 수 있다. -> 운영체제가 스케줄링에 개입하는 과정이 필요하지 않다.(일시 중단은 사용자가 제어할 수 있다.)
  • suspend()함수로 선언된 지연 함수여야 코루틴 기능을 사용할 수 있다. 내가 suspend 함수를 직접 만들어도 lauch와 async같은 코루틴 빌더 안에서만 사용가능하지 main 블록에서는 사용할 수 없다.
  1. launch로 코루틴 블록을 만들어 내기(코루틴 빌더의 생성)
    • 현재 스레드를 차단하지 않고 새로운 코루틴을 실행할 수 있게 하며 특정 결과값 없이 Job 객체를 반환한다.
  2. GlobalScope는 생명주기를 프로그램의 생명 주기에 의존된다.

    기본적인 실행 예제doWork1()과 doWork2()가 순차적으로 실행되는 것처럼 표현되었지만 내부적으로 비동기 코드로서 동시에 작동할 수 있다.

  3. async로 코루틴 빌더 생성하기
    • launch와는 다르게 Launch객체가 아닌 Deferred<T>를 통해 결괏값을 반환한다.doWork1과2는 async에 의해 감싸져 있으므로 완전히 병행 수행할 수 있다. 위 코드에서는 언제 끝날지 모르는 doWork1과2의 종료시점을 기다렸다가 결과를 받을 수 있도록 await()를 사용해 현재 스레드의 블로킹 없이 먼저 종료되면 결과를 가져올 수 있다.
      이런 기법은 안드로이드 UI스레드에서 블로킹 가능성이 있는 코드를 사용하면 애플리케이션이 중단되거나 멈추는 경우가 발생할 수 있는데, 이 경우 await()를 사용하면 UI를 제외한 루틴만 블로킹 되므로 UI가 멈추는 경우를 해결할 수 있다.
  4. runBlocking
    새로운 코루틴을 실행하고 완료되기 전까지 현재 스레드를 블로킹한다.
    메인 함수 자체를 잡기 위해 main() 함수 자체를 블로킹 모드에서 실행할 수 있다.다음과 같은 runBlocking은 클래스 내의 멤버 메서드에서도 사용할 수 있다.
  5. join()함수의 결과 기다리기
    Job객체의 join()함수를 사용하여 명시적으로 코루틴의 작업이 완료되는 것을 기다리게 할 수 있다.
  6. List나 repeat()함수를 사용하여 많은 작업을 처리하기.기본문법인 List(){}로 많은 양을 처리할때 스레드에서 실행하면 메모리 오류가 나오지만 코루틴은 내부적으로 단 몇 개의 스레드로 수많은 코루틴을 생성해 실행할 수 있기 때문에 오류가 발생하지 않는다. 위와 같이 실행하면 "."의 출력이 다 끝나고 repeat구문이 실행하는 와중에 "End"가 찍히게 된다.
  7. 표준 라이브러리 중 sequence()를 사용하여 아주 많은 값을 만들어 내는 코드로부터 특정 값의 범위를 가져올 수 있다.
    • sequence()함수는 Sequence<T>를 반환하는데 Sequence() 함수 내부에서 지연 함수를 사용할 수 있고 코루틴과 함께 최종 형태를 나중에 결정 할 수 있는 늦은 시퀀스를 만들 수 있다. -> 특정 요소가 완전히 구성되기 전에 사용 범위와 시점을 결정할 수 있다. -> 무제한 스크롤링을 구현하는 UI에 적용할 목록을 가져올 때 이용sequence 블록에서 yield()함수를 호출하면서 코루틴을 생성해야한다. yield는 산출한다는 의미로 각 표현식을 계속 진행하기 전에 실행을 잠시 멈추고 요소를 반환한다.

2. 코루틴 동작 제어하기

코루틴은 항상 특정 문맥에서 실행된다. 어떤 문맥에서 실행할지는 디스패처(Dispatcher)가 결정한다.

  1. launch(Dispatchers.Default)
    • GlobalScope.launch{...} 와 같은 표현으로 main이 종료될때 같이 종료된다.
    • 공유된 백그라운드 스레드의 CommonPool에서 코루틴을 실행하도록 한다. -> 스레드를 새로 생성하지 않고 기존에 있는 것을 이용한다.
    • 연산 중심의 코드에 적합하다.
  2. launch(Dispachers.IO)
    • 입출력 위주의 동작을 하는 코드에 적합한 공유된 풀
    • 블로킹 동작이 많은 파일이나 소켓 I/O 처리에 사용하면 좋다.
  3. Dispachers.Unconfined, newSingleThreadContext, coroutineContext, 아무런 인자를 넣지 않는것. 등이 있다.
  1. repeat() 함수를 사용한 반복 동작하기
    위와 같이 GloablScope안에 반복문을 넣으면 main이 끝나는 1300ms안에 총 3번의 println구문만 나오게 된다. 하지만 GlobalScope를 빼고 그냥 launch만 실행하게 되면 백그라운드에서 실행하는 일종의 데몬스레드를 구성할 수 있다.

  2. 코루틴 작업 취소하기
    Job의 join()과 다르게 .cancel() 함수를 사용하면 작업을 취소한다.

  3. finally의 실행 보장

  • try~finally 구문을 사용해 finally 블록에서 코루틴의 종료 과정을 처리하도록 할 수 있다.
  • 일반적으로 finally 블록에서 지연 함수를 사용하려고 하면 코루틴이 취소되므로 사용할 수 없다. -> 시간이 걸리는 작업이나 지연 함수가 사용될 경우 실행을 보장하기 위해 NonCancellable 문맥에서 작동하도록 해야한다.
  • withContext(NonCancellable) {...} 을 사용해 구성해준다.
  1. 실행 상태의 판단
  • 만일 코드를 중단하기 위해 코루틴에 조건식을 넣으려고 할 때 조건식의 연산이 마무리되기 전까지는 조건식에 의해 루틴이 중단되지 않는다
  • 조건을 계산에 의해 반복을 하게 되면 루프가 완료될 때까지 job.cancelAndJoin()을 해도 끝나지가 않는다. 그 때는 while(i<5)처럼 조건식보다 while(isActice)를 사용하여 종료한다.
  1. 코루틴의 시간 만료
    • 일정 시간 뒤에 코루틴을 취소할 수 있게 한다.예외 말고 null로 처리하고 싶은 경우 withTimeoutOrNull()을 사용한다.

3. 채널의 동작

  • 채널은 자료를 서로 주고 받기 위해 약속된 일종의 통로 역할을 한다.
  • 코루틴은 넌블록킹 전송 개념으로 사용되고 있다.
  • send()와 receive() 함수를 사용한다.주의점
  1. 송신자는 SendChannel에서 채널이 꽉 차 있는지, 즉 isFull 값이 true인지 살펴보고 꽉 차 있으면 일시 지연된다.
  2. close()에 의해 닫으면 isClosedForSend가 true로 지정되어 isFull은 false를 반환할 수 있다.
  3. 수신자는 isEmpty가 비어있으므로 가져갈 게 없는 루틴은 일시 지연된다.
  4. 마찬가지로 닫을 경우 isClosedForReceive에 의해 false를 반환할 수 없다.
  • 그외 SendChannel.offer(), ReceiveChannel.poll()이 있다.

채널의 종류
1. RendezvousChannel : 내부에 버퍼를 두지 않는 채널. 모든 send 동작은 receive가 즉각 가져가기 전까지는 일시 중단된다.
2. ArrayChannel : 특정한 크기로 고정된 버퍼를 가진 채널. 해당 버퍼가 꽉 차기 전까진 send가 지연되지 않고 보낼 수 있게 된다. receive도 마찬가지이다.
3. LinkedListChannel: 버퍼의 크기에 제한이 없어 send 시 일시 중단인 상태를 가지지 않는다. 다만 send를 지속할 경우 메모리 부족 오류를 만날 수 있다. receive는 비어 있는 경우 일시 중단된다.
4. ConflatedChannel: 버퍼는 하나의 요소만 허용하기 때문에 모든 send 동작은 일시 지연되지는 않습니다. 다만 기존의 값을 덮어 씌운다.

produce 생산자 소비자 패턴

  • produce는 채널이 붙어 있는 코루틴으로 생산자 측면의 코드를 쉽게 구성할 수 있다.
  • 채널에 값을 보내면 생산자로 볼 수 있고 소비자는 consumeEach 함수를 확장해 for 문을 대신 해서 저장된 요소를 소비한다.위 코드에서 produce<E>는 값을 생산하고 ReceiveChannel<E>를 반환한다. 그런 다음 result에서 ReceiveChannel의 확장함수인 consumeEach를 사용하여 각 요소를 처리한다.

버퍼를 가진 채널

  • 채널에는 기본 버퍼가 없으므로 send()함수가 먼저 호출되면 receive()함수가 호출되기 전까지 send()함수는 일시 지연된다. 반대의 경우도 마찬가지로 지연된다.
  • 하지만 채널에 버퍼 크기를 주면 지연 없이 여러 개의 요소를 보낼 수 있게 된다.
  • Channel() 생성자의 capacity 매개변수가 버퍼 크기를 정한다.

select 표현식

  • 다양한 채널에서 무언가 응답해야 한다면 각 채널의 실행 시간에 따라 결과가 달라질 수 있는데 이때 select를 사용하면 표현식을 통해 결과를 받을 수 있다.produce로 만든 2개의 루틴은 무작위로 지정된 시간에 각각 A,B라는 문자열을 채널에 보내게 된다. 이때 select 블록의 onReceive를 통해 채널로부터 이 값을 받아 먼저 완성된 결과를 가져오게 된다.

4. 공유 데이터 문제

자바에서 했던것과 같이 코틀린에서는 @Synchronized를 통해 특정 스레드가 이미 자원을 사용하는 중이면 나머지 스레드에서 접근 막는 방식을 사용할 수 있다.
여기서 자바의 volatile처럼 코틀린에서 @Volatile을 사용하여 데이터를 캐시에 넣지 않도록 선언해줄 수도 있다.
volatile 키워드를 사용하면 코드가 최적화되면서 순서가 바뀌는 경우도 방지할 수 있다.
synchronized와 volatile을 같이 사용하여 원자성을 보장해줘야 한다.

원자 변수

  • 원자 변수란 특정 변수의 증가나 감소가 단일 기계어 명령으로 수행되는 것을 말하며 해당 연산이 수행되는 도중에는 누구도 방해하지 못하지 때문에 값의 무결성을 보장할 수 있다.

    만약 다음과 같은 코드가 있다면 counter++는 컴퓨터 내부적으로는 여러 과정을 거쳐야하는데 병렬실행중인 코루틴이 이 과정을 도중에 끊어버리고 counter++를 다시 실행하게 된다. 그래서 총 1000000번중 랜덤하게 실행 결과가 나오게 된다.

  • 즉, 아래의 코드와 같이 원자 변수와 전용 메서드를 사용하여 값의 무결성을 보장하고 예상할 수 있는 값으로 실행해야 한다.

스레드 가두기

  • 특정 문맥에서 작동하도록 단일 스레드에 가두는(Thread Confinement)방법이 있다.
  • 보통 UI 애플리케이션에서 UI 상태는 단일 이벤트에 따라 작동해야 한다. -> 단일 스레드 문맥인 newSingleThreadContext를 사용할 수 있다.각 스레드는 문맥상 counter를 독립적으로 가지며 처리하기 때문에 공유 변수 counter의 연산의 무결성을 보장할 수 있다.

상호 배제

  • 상호 배제(Mutual Exclusion)는 코드가 임계 구역(Critical Section)에 있는 경우 절대로 동시성이 일어나지 않게 하고 하나의 루틴만 접근하는 것을 보장한다.
  • 소유자(Owner)의 개념으로 일단 잠근 루틴만이 잠금을 해제 할 수 있다.
  • 자바에서는 비슷하게 synchronized 키워드를 사용했지만 코틀린의 코루틴에서는 Mutex의 lock과 unlock을 사용해 임계 구역을 만들 수 있다.
  • 람다식 withLock을 사용하면 mutex.lock(); try{...} finally { mutex.unlock() }와 같은 패턴을 쉽게 사용할 수 있다.

actor 코루틴 빌더

  • 코루틴의 결합으로 만든 actor는 코루틴과 채널에서 통신하거나 상태를 관리한다. 다른 언어의 actor 개념은 들어오고 나가는 메일 박스 기능과 비슷하지만 코틀린에서는 들어오는 메일 박스 기능만 한다고 볼 수 있다.

    메일박스란?
    특정 연산, 상태, 메시지 등을 담아 보내는 것으로 위치에 상관없이 완전히 비동기적으로 수행되도록 디자인된 개념

profile
배운거 정리하기

0개의 댓글