본문 바로가기

Language/Common

Coroutine

Main Routine


Program이 실행될 때 불려지도록 만들어진 program의 중심이 되는 일련의 code들을 뜻한다.

C++에서는 main함수가 main routine이다.


Subroutine


Main Routine이외의 다른 모든 routine

즉 main함수 외의 모든 함수


Coroutine


진입하는 지점을 여러개 가질 수 있는 subroutine

Coroutine은 호출한 routine을 대등한 관계로 호출할 수 있기때문에

다른 Routine의 종속관계가 아니다. 라고 표현할 수 있다.


Coroutine은 함수 내에서 호출한쪽을 다시 호출할 수 있고 다시 다른 routine에서 함수의 중간 지점을 호출할 수 있는것


Main Routine과 Subroutine

Coroutine




Coroutine은 Thread와 비슷한가?


보통 Coroutine을 Thread와 비슷한 개념이라고 말하는데 비슷한 면은 많다.

Thread와 Coroutine 모두 자신만의 stack이 존재하고 실행 순서를 가진다.

하지만 차이점도 있다.


Thread란 program내에서 실행되는 흐름의 단위로서 모든 program은 최소 하나의 thread를 가지며

이 thread를 Main Thread라 한다. 그리고 이 Main Thread에서 Main Routine이 호출된다.

Thread는 흐름의 단위이기 때문에 새로운 thread가 생성되었다는 것은 새로운 시간 흐름이 만들어졌다고도 볼 수 있다.

이렇게 program은 여러개의 thread를 동시에 실행할 수 있고, 이것으로 인해 일종의 흐름이 동시에 진행될 수 있다.(Multi thread)




독립적인 시간 흐름을 가지고 routine을 실행하는 것이 바로 thread인것이다.

그래서 보통의 routine은 시작부터 끝까지 하나의 thread에서 실행되지만 Coroutine은 호출자를 다시 호출할 수 있고 진입 지점을

여러개 가질 수 있다는 특성때문에 여러 thread에서 하나의 Coroutine이 실행될 수 있다.

이러한 특성때문에 Coroutine은 비동기 로직 처리에 유용하게 사용될 수 있다.


C++에서의 Coroutine은 정식으로 지원되지 않고 boost에서 지원한다.

Coroutine은 독립적인 정보를 가져야한다. Routine이 중간에 빠져나갔다가 다시 호출되었을 떄 

이전의 Coroutine에서 생성한 local 변수나 연산들이 정상적으로 작동해야 하기 때문이다.

그래서 C++에서의 Coroutine은 독립적인 Stack을 가진다. 그리고 Coroutine을 호출하거나 빠져나갈때 

Contextswitching이 일어난다.






C++에서의 Coroutine의 문제점


C++에서 제대로된 Coroutine을 구현하려면 일정크기의 Stack을 미리 할당해 두어야하는것이다.

그리고 이렇게 할당된 stack은 대부분의 경우 stack memory를 모두 사용하였을때 늘릴 수 없다.

그래서 Coroutine을 처음 생성할 떄 충분히 큰 memory를 할당해 두고 사용하게 된다.

Coroutine을 한두개 생성하는 정도라면 thread처럼 1MB씩 stack을 할당해서 사용해도 충분하지만

수천개씩 만들어야 하는 경우라면 memory를 크게 할당할 수 없다.


그래서 boost coroutine의 stack기본 memory값은 windows일경우 64kb로 되어있다.

이유는 Allocation Granularity Boundary(할당 임계 영역)때문인데

이것은 memory 할당의 시작주소가 될 수 있는 기본단위이다.

따라서 64kb보다 더 작은 사이즈로 할당하는 것은 memory 조각화를 발생시키는 요인이다.




C++에서 왜 Stack을 키우는것이 어려운가


memory가 부족할때 2배씩 재 할당하는 방법은 흔히 memory를 키워나가는 방식이다.

간단하게 생각해보면 stack overflow가 발생하는 상황에 memory를 재할당 받고 copy하는 방법으로 구현할 수 있을것이다.

이런 방식으로 동작하는 대표적인 예로 vector가 있다.


그런데 이런 방식에는 문제점이 있다. C++객체 모델에서는 type metadata없이는 객체의 복사생성자를 호출할 수 없고

따라서 제대로 된 복사를 할 수 없다.

예를들어 type metadata없이 memcpy같은 방식으로 copy를 수행하게 되면 stack에 

int a;

int *b = &a;

이렇게 값이 올라가 있는 경우 재할당 후에 copy를 하고 나면 b는 다른 stack위를 가리키게 되는 문제가 발생한다.



그렇다고해서 stack을 키우는 것이 불가능한 것은 아니다.

GCC 4.6의 경우 Split Stack이라는 stack을 분할하여 사용하는것을 지원한다.




결론


C++에서 Coroutine을 사용하는 경우에는 Context Switching 비용을 기반으로 Coroutine의 생성 및 관리를 하는 것 보다는

해당 Coroutine에서 사용되는 stack size를 기반으로 관리를 해야한다.









참고


https://gamedevforever.com/291

'Language > Common' 카테고리의 다른 글

COM / DCOM / COM+ / IPC  (0) 2019.03.05
Unmanaged / Managed Pointer, Heap  (0) 2019.03.05