코루틴 공식 가이드 자세히 읽기 — Part 3— Dive 1

Myungpyo Shim
4 min readOct 21, 2019

--

producer-consumer 구현을 돕기위한 produce-consumeEach 확장 함수는 어떻게 구현되어 있을까?

Korean [English]

(목차로 돌아가기)

공식 가이드 Part 3에서는 채널을 통한 코루틴 간 데이터 교환에 대해 다룬다. 우리는 채널을 생성하여 코루틴 간에 이 채널을 공유함으로써 데이터를 송/수신 할 수 있고 이는 자주 필요한 상황이기 때문에 이를 위한 함수 정의를 고려해 볼 수 있다. 이런 이유로 코루틴 프레임워크에서는 이미 이를 위한 produce 와 consumeEach 함수가 정의되어 있다. 이 두 함수의 역할에 대해서 자세하게 살펴보자.

이들의 역할을 살펴보기 위한 예제 코드는 다음과 같다.

produceSquares 함수는 1부터 파라미터로 전달 받은 수까지 각 수의 제곱을 송신하며 이를 수신받을 수 있는 수신채널을 반환한다. 이 함수가 사용하고 있는 produce 확장 함수는 코루틴 프레임워크에 구현되어 있으며, 다음과 같다.

produce 는 CoroutineScope의 확장함수이며 context 와 capacity(버퍼 용량)는 특별히 주어지지 않으면 기본 값인 EmptyCoroutineContext와 0을 사용한다. 예제에서는 produce { } 와 같이 위 두 파라미터를 기본값을 사용하도록 호출 하고 있으며, 마지막 파라미터인 block 을 lambda 형태로 전달하고 있다.

block 은 중단함수(suspend)이며 ProduceScope<E> 의 확장함수로 정의 된다. ProduceScope는 다음과 같이 CoroutineScope과 ProducerChannel을 구현하는 인터페이스 이다.

위의 구현에서 우리는 produce { } 에 전달되는 블록, 즉 우리가 구현한 함수에서 SendChannel<T> 에서 정의된 함수들을 사용할 수 있음을 알 수 있고(ex> send, close) 이런 함수들은 ProduceScope 에 정의 된 val channel: SendChannel<E> 채널을 통해서 해당 동작을 수행할 것이라는 점을 알 수 있다.

위 내용으로 미루어보면 우리가 produce 함수로 전달 한 코드블록은 ProducerScope 에서 호출 될 수 있다는 이야기인데, 우리는 이 스코프에서 실행되는 코루틴을 정의한 적이 없다. 어떻게 우리 코드 블록이 실행되는 것일까?

앞서 제시한 produce 함수 정의로 다시 돌아가 그 구현을 분석해보자. 구현부를 살펴보면 우선 파라미터로 전달 된 capacity 만큼의 버퍼를 갖는 채널을 생성하고, 파라미터로 전달 된 context를 이용하여 새로운 컨텍스트를 생성한다. 이 두 값을 이용하여 ProduceCoroutine을 생성하여 시작시키고, 코루틴을 반환한다. 함수의 반환 타입이 ReceiveChannel<E> 인데 이 코루틴을 반환할 수 있는 이유는 ProduceCoroutine이 다음과 같이 ChannelCoroutine을 상속하고 있기 때문이다. (ChannelCoroutine은 ChannelCoroutine->Channel->Send&ReceiveChannel 의 상속구조를 갖는다)

우리가 produce { } 에 전달한 마지막 파라미터인 채널 데이터를 송신하는 블록은 이 ProduceCoroutine에서 실행되게 되며 send 함수를 호출하면 produce 함수에서 정의되었던 채널에 데이터를 송신할 수 있다.

위와 같은 방식으로 produce 함수를 통한 데이터 송신이 가능하고, 이렇게 송신된 데이터는 produce { } 함수의 반환값인 ReceiveChannel 의 데이터를 consume 함으로써 가능하다.

consumeEach를 이용하면 채널에 전달된 데이터들에 특정 동작을 수행할 수 있는데, 우리 예제에서는 단순히 데이터를 출력하고 있다. consume과 consumeEach의 구현은 다음과 같다.

consume 함수는 수신채널에서 데이터를 수신하여 반환하는 블록을 파라미터로 전달받아 수신한 데이터를 반환한다. consumeEach는 단순히 수신하는 데이터를 순회하며 전달된 블록(액션)을 수행한다.

끝.

--

--