코루틴과 파이버

Jooyung Han (한주영)
5 min readDec 10, 2017

--

이전 글: 코루틴을 구분해보자

코루틴을 이야기하다보면 파이버(Fiber)를 지나칠 수 없을 듯. 얼핏 비슷한 것 같기도 하고 다른 것 같기도 하고…

C++ 의 코루틴 표준을 준비하면서 만들어진 문서 중에 “Distinguishing coroutines and fibers” 라는 문서가 있다. 이것이 정답이다~ 는 것은 아니고, 참고할만 하다고 보인다. 이 글에서는 이 문서의 내용을 정리해본다. (물론 위키백과 fiber 페이지에도 비슷한 설명이 나온다.)

파이버

먼저 fiber가 뭔지 살펴보자.

파이버는 user-space thread인데, 하나의 쓰레드 위에 여러 개의 파이버가 협력적 멀티태스킹을 수행하는 동시성 프로그래밍 요소이다. 쓰레드에 매핑되는 여러가지 개념들이 같이 등장한다. 스케줄러나 뮤텍스 등이 그렇다.

쓰레드처럼, 파이버들끼리는 주소공간을 공유한다. 하지만 파이버는 협력적 멀티태스킹 방식을 사용한다.(쓰레드는 선점형 멀티태스킹) 쓰레드의 경우엔 커널의 쓰레드 스케줄러에 의해 작업 중이라도 멈추고 다른 쓰레드로 전환되지만 파이버의 경우엔 다른 파이버로 전환하려면 실행 중인 파이버가 직접 자신을 멈추어야 한다. — 위키피디어 “파이버”

코루틴을 경량의 쓰레드 정도로 설명하는 경우를 종종 볼 수 있는데, 사실 가벼운 쓰레드에 적합한 말이 파이버다. (실제로 실(thread), 섬유(fiber) 라는 어휘 선택을 봐도 “쓰레드"와 연관지어 설명한다면 fiber가 적절하다.)

파이버가 IO로 블로킹(소프트 블로킹)되면 제어권은 파이버 스케줄러에 넘어가며 스케줄러는 다른 파이버들 중에서 ready 상태인 녀석에게 다시 제어권을 넘긴다.

파이버 간의 컨텍스트 스위칭은 user-space에서 발생하기 때문에 오버헤드가 적다. 그리고 당연하게도 같은 쓰레드 상에서 실행되는 여러 파이버들은 동시에 실행되지 않기 때문에 마치 race condition과 같은 이슈가 없다. 어떤 파이버 하나가 CPU를 점유하고 제어권을 내놓지 않으면 다른 파이버는 굶주릴 수 밖에 없다.

파이버는 프로세스에서 쓰레드로 이어지는 사고를 한 단계 더 이어나간다.

  • 한 컴퓨터에 여러 개의 프로세스가..
  • 한 프로세스에 여러 개의 쓰레드가..
  • 한 쓰레드에 여러 개의 파이버가 실행된다.

파이버가 위력을 발휘하는 경우는 IO 블로킹이 많은 여러 개의 연산들을 동시에 진행하는 경우다. 그리고 실제로 이것이 위력을 발휘하려면 쓰레드 전체를 블로킹하지 않는 비동기 IO가 뒷받침되어야 한다.

코루틴

코루틴은 동시성 프로그래밍에 대한 직접적인 언급이 없으며 단지 제어 흐름만 이야기할 뿐이다. 대칭 코루틴이라면 코루틴이 suspend하면서 제어를 넘겨받을 코루틴을 지정할 것이고, 비대칭 코루틴이라면 caller에게 제어가 되돌아갈 것이다.

코루틴은 함수 호출이나 for/if/goto 같은 제어 흐름 요소이다.

대표적인 응용 사례로는 generator 코루틴 같은 것이 있다. generator는 값을 쓰는 역할을 하고, caller는 값을 읽는 역할을 하는데, 절묘하게 각각이 push와 pull 방식을 동시에 사용할 수 있다.

generator numbers(n) {
while (true) yield n++; // push
}
subroutine useNumbers() {
var iter = start numbers(1);
while (iter.hasNext()) { use(iter.next()); } // pull
}

push/pull 모델을 동시에 지원하기 때문에 데이터 변환의 파이프라인을 만들기도 쉽다.

consume(filter(producer()))

비교

분명히 설명에서 겹치는 부분이 있다. Cooperative multitasking은 코루틴을 설명할 때 나오는 대표적인 표현이 아닌가! 파이버는 스스로 제어를 내놓아야 하고, 스케줄러에 의해 재시작될 때 기존의 상태를 그대로 유지한다. 이 설명 역시 코루틴의 동작을 설명한 것 같다.

정리하자면, 코루틴은 제어 흐름에 관한 언어적 요소이며, 파이버는 동시성 프로그래밍을 위한 시스템적 요소라는 점. 파이버를 가능하게 하는 언어적 요소가 바로 코루틴 제어 흐름이라는 것이다. 흥미로운 건, boost.fiber와 같은 파이버 구현이 정말 boost.coroutine 라이브러리를 사용한다는 점이다. 즉 코루틴의 제어 흐름을 동시성 프로그래밍 요소로 응용한 것이 파이버라고 볼 수 있다. (여기서 Boost.coroutine 구현은 “코루틴 소개" 글에서 잠깐 언급한, C++에서 코루틴 표준을 놓고 경쟁하는 두 가지 코루틴 방법 중 하나. 상태 머신으로 변환하는 stackless 코루틴이 아니라 콜스택을 관리하는 stackful 코루틴이다.)

반대로 파이버로 코루틴을 구현할 수도 있을 것이다. 하지만 이미 동시성 요소가 가미된 것에서 그 의미를 덜어내는 것보다, 코루틴의 제어흐름을 이용하여 동시성 요소를 가미하는 것이 더 자연스러워 보인다.

다음 글: Knock!Knock! 코루틴

딴소리 1

Go의 고루틴은 코루틴에서 따왔을 게 뻔하지만 사실 여기서 이야기한 fiber에 가깝다고 보인다.

딴소리2

React의 Fiber는 그 이름으로부터 바로 유추 가능한 중요한 특성이 있다. 바로, 연산을 멈췄다가 나중에 재개할 수도 있다는 것. 즉 suspend/resume이 가능할 것이라는 점이다.

--

--

Jooyung Han (한주영)

가끔 함수형 프로그래밍 관련 글을 쓰거나 번역합니다. “개미 수열을 푸는 10가지 방법"이란 책을 썼습니다. https://leanpub.com/programming-look-and-say