1. 동시성과 병렬성
코루틴을 공부하기 전에 동시성과 병렬성을 알고 있어야 합니다.
1-1. Concurrency 동시성
동시성은 이름만 보면 무언가가 동시에 실행되는 것처럼 생각할 수 있지만 실제론 하나의 시스템이 여러 작업을 동시에 처리하는 것처럼 보이게 하는 것이라는 의미를 갖고 있습니다.
동시성은 비동기 프로그래밍, 코루틴 등등을 사용해 구현됩니다. 동시성은 여러 작업들을 서로 바꿔가며 진행하기 때문에 여러 요청들을 동시에 처리하거나 서로 번갈아가며 처리할 수 있습니다.
1-2. 병렬성
병렬성은 여러 작업을 물리적으로 동시에 처리하는 것을 말합니다. 여러 cpu나 코어 등을 이용해서 여러 작업을 병렬로 처리합니다.
동시성 | 병렬성 | |
구분 | 동시에 처리하는 것처럼 보이게 하는것 | 물리적으로 동시에 처리하는것 |
동작 | 싱글 코어에서 멀티 스레드가 가능 | 멀티 코어에서 멀티 스레드를 동작 |
2. 코루틴
코루틴은 동시성으로 작동하며 비동기 프로그래밍을 지원하는 방식입니다.
async def로 코루틴 함수를 선언하며 해당 함수는 코루틴 객체를 반환하게 됩니다.
>>> async def a():
... return a
...
>>> a()
<coroutine object a at 0x100eccd50>
2-1. 코루틴과 비동기 프로그래밍
코루틴은 쉬운 방법으로 간단하게 비동기 프로그래밍을 할 수 있어서 매우 유용합니다. 기존의 동기적인 코드에서 발생하는 큰 i/o 작업 같은 문제들을 쉽고 효율적으로 해결할 수 있습니다.
웹 서버가 클라이언트의 요청을 처리할 때 동기적인 방식으론 한 번에 하나의 요청만을 처리가 가능합니다. 하지만 코루틴을 사용하면 동시에 여러 요청들을 처리할 수 있기 때문에 서버의 응답 속도가 높아질 수 있습니다.
2-3. 코루틴과 스레드
코루틴은 스레드와 비슷한 면도 있고 여러 부분으로 비교되기 때문에 어떤 차이가 있는지 정리해 봤습니다.
스레드는 각자 스택 메모리를 할당받습니다. 그리고 여러 task를 동시에 수행해야 할 때 무엇을 먼저 실행하거나 더 효율적인지를 계산하기 위한 선점 스케줄링을 해야 합니다. 이 과정에서 콘텍스트 스위칭에 대한 비용이 발생하고 레지스터, 스택 포인터, 프로그램 카운터등을 사용하고 반환하는데 비용이 사용됩니다.
스레드는 동시성을 콘텍스트 스위칭으로 보장합니다. 스레드 1에서 A를 실행중일 때 스레드 2의 결과가 필요할 때 비동기적으로 스레드 2를 호출하게 됩니다. 이때 스레드 1은 블로킹되고 스레드 2로 콘텍스트 스위칭이 일어나 B를 실행하게 됩니다. 다시 B가 완료되면 다시 스레드 1로 콘텍스트 스위칭을 하고 결괏값을 A에 반환합니다. 이때 다른 스레드 3, 4는 선점형 스케줄링을 통해서 동시성을 보장받게 됩니다.
선점형 스케줄링, 비선점형 스케줄링
선점형 스케줄링은 스레드의 실행을 선점합니다. 어떤 스레드가 작업 중이라도 스케줄러가 멈추고 다른 스레드를 실행하는 게 가능합니다. 대표적인 알고리즘으로 Shortest Job First, Round Robin, Priority Scheduling 등등이 있습니다.
비선점형 스케줄링은 스케줄러가 실행 중인 작업을 임의로 중단하지 않는 방식입니다. 실행 중인 작업이 임의로 양보하지 않는 이상 다른 작업이 실행되지 않습니다. await, yield 같은 키워드로 실행을 임의적으로 양보해야 합니다.
코루틴은 작업 하나하나에 스레드를 배정하지 않고 코루틴 객체를 할당해 주고 코루틴 객체들을 스위칭하면서 실행됩니다. 코루틴은 비선점형 스케줄링을 사용합니다. 개발자들이 직접 스위칭을 await, yield 등을 호출하여 해당 키워드가 호출될때 실행이 양보됩니다. 이는 메모리 사용량을 줄이고 콘텍스트 스위칭 비용을 줄였습니다.
코루틴도 객체이기 때문에 힙메모리에 저장됩니다.
코루틴은 콘텍스트 스위칭을 사용하지 않습니다. 개발자가 코드를 통해서 코루틴 객체 간의 스위칭을 마음대로 제어하기 때문에 os의 스케줄러가 관여하지 않습니다.
코틀린에서의 코루틴, 와 Python의 코루틴은 다르게 작동됩니다.
2-4. 코틀린의 코루틴
코틀린의 코루틴은 경량 스레드(Lightweight Thread)라고도 불립니다.
일반적으로 하나의 작업에서 다른 작업의 결과를 기다리게 되면 해당 스레드가 블로킹 상태가 됩니다. 하지만 코루틴에서는 스레드가 멈추지 않고 해당 작업을 Suspend합니다. 그래서 해당 스레드에서 다른 코루틴을 수행할 수 있습니다. 즉 스레드를 바꾸지 않고 같은 스레드에서 코루틴을 여러 개 실행이 가능합니다. 코루틴 간의 전환은 콘텍스트 스위칭 없이 진행되기 때문에 코틀린에서의 코루틴은 경량 스레드라고 불립니다. 물론 내부적인 상태 저장, 변환의 비용은 필요합니다.
하지만 이 경우에서도 만약 여러 스레드가 동시에 코루틴을 실행되게 된다면 동시성 보장을 위해 콘텍스트 스위칭이 발생할 수밖에 없습니다. 즉 코틀린의 코루틴에선 콘텍스트 스위칭을 하지 않기 위해서 단일 스레드에서 여러 코루틴 객체를 컨트롤하는 것을 권장합니다.
2-5. 파이썬의 코루틴
파이썬에선 yield 기반 혹은 asyncio기반의 async await을 활용한 코루틴 방식이 있습니다.
파이썬의 코루틴, asyncio의 핵심은 Event Loop입니다. 이벤트 루프는 비동기 코드 실행을 관리하며 싱글 스레드에서 코루틴의 작업들을 컨트롤합니다. 이벤트 루프는 기본적으로 하나만 생성될 수 있으며 해당 이벤트 루프에 task 등을 등록하며 작동합니다.
async 키워드로 코루틴 객체를 생성되어도 실행되진 않습니다. asyncio.run()을 통해서 이벤트루프를 생성하고, 코루틴을 실행하게 됩니다.
3. 결론
실제로 asyncio를 잘 모르고 사용하다가 이벤트 루프에 관한 여러 에러들을 만났었는데 항상 개념이 가장 중요한것 같습니다.
멀티스레드를 사용하지 않는 상황에서도 쉽게 비동기 프로그래밍을 할 수 있으니 코루틴을 사용해보는것도 좋을 것 같습니다.