python 동시성 관리 (3) - 코루틴(Coroutine)이란?

3

Python 동시성관리

목록 보기
3/3
post-thumbnail

들어가기에 앞서

이번 시간에는 코루틴 개념에 관하여 알아보도록 하겠습니다.

  • 프로세스(Process)와 스레드(Thread) 개념
  • Python GIL(Global Interpreter Lock)
  • 코루틴(Coroutine)
  • 동시성 관리 구현에 유용한 모듈 예제(Futures, asyncio)
                           [이 글은 Python 언어 기반으로 작성되었습니다.]

Python 동시성 관리를 이해하기 위하여 알아야 할 것들

먼저 동시성 관리를 설명하기 전에, 기본적으로 이해해야 할 것들이 존재합니다.
크게 3가지에 대해서 설명할텐데요! 3가지는 다음과 같습니다.

  • 프로세스와 스레드 개념
  • Python GIL(Global Interpreter Lock)
  • 코루틴(Coroutine)

세가지 개념 중 코루틴(Coroutine) 개념에 대해서 이해해보도록 하겠습니다.

동시성(Concurrency)과 병행성(Parallelism)

우선 코루틴에 대해 이해하기 전에, 동시성과 병행성에 대해서 정확히 이해하고 넘어갈 필요성이 있어 설명하고 넘어가려고 하는데요.

혹시 1편에서 다음과 같은 화면을 기억하시나요?
( 1편을 보시지 못하신 분은 python 동시성 관리 (1) - 프로세스(Process)와 스레드(Thread) 을 참고 부탁드리겠습니다)

위의 상황을 다시 생각해보면서 동시성과 병행성에 대해서 설명해보도록 하죠!

앞서 프로세스는 최소 하나 이상의 프로세서(CPU)에서 실행될 수 있다고 말씀드렸습니다.
제 노트북의 코어 개수는 8개이므로, 최대 동시에 8개의 프로세스가 실행 가능하다고 말씀드릴 수 있겠네요.

하지만 제가 노트북에서 작업을 할 때 8개보다 훨씬 더 많은 프로세스를 실행할 수 있는 이유는
실제 동시에 실행되는 것이 아니라, 운영체제(OS)가 정말 빠른 속도로 CPU가 실행할 프로세스를 교체하면서 실행하고 있다고 했습니다.

이 때 동시성(Concurrency)병행성(Parallelism)에 대해서 설명드릴 수 있는데요!

동시성(Concurrency)은 특정 순간에 동시에 작업이 수행되는 것이 아니라, 마치 여러가지 일을 동시에 하는 것처럼 느껴지게 하기 위하여 시분할 처리를 하는 것을 의미합니다.

반면에 병렬성(Parallelism)은 실제 순간에 동시에 작업이 수행되어 병렬로 수행되는 것을 의미하죠.

즉, 동시성과 병렬성은 다른 개념인 것입니다.

다른 예를 들어보면, 앞서 2편에서 GIL이 적용되어 있는 CPython 의 경우는 멀티스레딩이 수행시 CPU 입장에서 본다면 병렬성이 아닌 동시성이 적용된 경우이라고 할 수 있습니다.

만약 GIL 환경에서 병렬성을 적용하고 싶은 경우에는 여러 개의 CPU가 주어진 환경에서 멀티프로세싱으로 병렬성을 적용할 수 있는 것이죠!

그렇다면 동시성 관리란, 병렬성이 포함되지 않는 개념일까요?

이는 사실 사람마다 혼재하여 사용하고 있습니다.
"동시성 관리를 한다"라고 하면 동시성(Concurrnecy)와 병렬성(Parallelism)을 포함한 의미일수도 있고,
순수 동시성(Concurrency)를 의미할 수도 있으니 이는 문맥에 따라서 파악이 필요합니다.

다음의 그림을 통해 한번 더 차이를 직관적으로 이해해 보시길 바랍니다.

앞서 동시성과 병렬성을 구분하여 설명드린 이유는,

앞으로 설명드릴 코루틴(Coroutine)은 병렬성이 아닌 동시성(Concurrency) 프로그래밍을 지원하는 기술이기 때문입니다.

코루틴에 대한 위키피디아 설명 중 스레드와 비교 설명에서 다음과 같은 설명이 들어가 있습니다.

Coroutines are very similar to threads. However, coroutines are cooperatively multitasked, whereas threads are typically preemptively multitasked. Coroutines provide concurrency but not parallelism.(코루틴은 동시성은 지원하지만 병행성(병렬성)은 지원하지 않는다)

동시성과 병렬성의 개념에 대한 구분도 중요하기도 하고,
또 코루틴을 이해하는데 위의 내용 때문에 헷갈릴 수가 있어 먼저 설명드리고자 말씀드렸습니다!

그럼 이제 본격적으로 코루틴(Coroutine)에 대해서 알아보도록 하죠.

코루틴이란?

코루틴(Coroutine)이란, Co + Routine의 의미로서, 상호협력하는 루틴이라고 볼 수 있습니다.
또는 상호 연계 프로그램, 또는 함수를 일컫기도 합니다.

여기서, 상호협력하는 루틴이란 무엇일까요?
두 함수를 비교해서 한번 직관적으로 이해해보겠습니다.

먼저, 두 개의 수를 더하는 함수를 호출하는 메인 함수를 코드로 작성해보겠습니다.

def add(a, b):
	c = a + b
    print(c)
    print("add 함수")

def calc():
	add(1, 2) 
    print("calc 함수")
    
calc()

위의 코드를 보면 calc 함수 안에서 add 함수를 호출하고
add 함수가 끝나면 다시 calc로 돌아옵니다.
add 함수가 끝남과 동시에 add 함수에 들어있던 변수와 계산식은 모두 사라지게 되죠.
이를 우리는 보통 메인 루틴에서 서브 루틴을 호출하고 서브 루틴은 종료된다라고 얘기할 수 있습니다.

그림으로 간단히 표현해보자면 다음의 관계처럼 표현할 수 있겠네요.

하지만, 코루틴은 방식이 조금 다르다고 할 수 있습니다.
위처럼 메인 루틴과 서브 루틴이 종속적인 관계가 아니라, 서로 대등한 관계이며 특정 시점에 상대방의 코드를 실행합니다.

아래 코드가 코루틴 예시 중 하나입니다.

def sum_coroutine():
    total = 0
    while True:
        x = (yield total)
        total += x


def sum_func():
    co = sum_coroutine()
    next(co)
    result1 = co.send(10)
    print(f"result1 --> {result1}")
    result2 = co.send(20)
    print(f"result2 --> {result2}")

sum_func()

sum_funcsum_coroutine 의 객체 co가 서로 값을 주입하고 결과를 전달받아 상호 협력적으로 작동하는 것을 보실 수 있습니다.

중요한 것은 sum_coroutine이 한번 실행 후 종료되는 것이 아니라, 계속 co에게 send 함수를 통해 값을 전달하고 "대기"하고 있는 것을 확인하실 수 있습니다.

(뒤에 코루틴을 위한 문법등을 자세히 다루니, 잘 모르겠더라도 너무 걱정하지 마세요!
개념만 확실히 이해하시고 넘어가시면 됩니다.)

그림으로 보면 다음의 관계로 표현할 수 있습니다.

위와 같은 성질 때문에 코루틴을 협력형 멀티 테스킹을 위한 함수 또는 프로그램이라고 합니다.

추가적으로 sum_coroutine이 한번 실행하고 종료되는 것이 아니라, "대기" 하고 있다고 말씀드렸는데요,
이는 즉 코루틴은 비동기 처리가 가능하도록 하며, 코루틴 함수들을 잘 조합하여 사용하면, 동시성(Concurrency) 프로그래밍 또한 가능합니다

이처럼 코루틴은 기본적으로 특정 지점에서 실행을 일시 중지할 수 있는 함수이며, 나중에 원할 때마다 같은 지점에서 실행을 재개할 수 있습니다.

코루틴과 스레드의 비교

일반적으로 코루틴과 스레드를 비교하여 설명하는데요,
보통 코루틴을 정의할 때 코루틴은 경량 스레드이다(light-weighted thread) 라고 말하기도 합니다.

그 이유는 코루틴과 스레드는 둘 다 동시성 프로그래밍이 가능한데요,
스레드는 동시성을 보장하는데 있어 Context switching 이라는 비용과 메모리 사용이 발생하는데 반면에
코루틴은 Context switching이 발생하지 않고 메모리 사용을 줄인 채로 동시성을 보장할 수 있기 때문에 경량 스레드라고 표현하고 있습니다.
코루틴은 프로그래머의 코드를 통해 동시성을 보장하는 것이지요.

조금 더 자세한 이해를 위해 두 그림을 통해 살펴보도록 하겠습니다.
두 그림 모두 Task1, Task2, Task3, Task4 를 수행하기 위하여 스레드와 코루틴을 사용한 그림인데요!

먼저 스레드부터 살펴보죠.

ThreadA 에서 Task1을 수행하다가 Task2 수행이 필요할 시에 비동기적으로 ThreadB를 호출(request)하여 Task2를 수행합니다. 이 때 ThreadA는 block되며 ThreadB 로 Context Switching이 일어납니다.

task2가 완료(Finished)되면 다시 ThreadA로 Context switching이 일어나 결과값을 Task1로 반환합니다.
마찬가지의 방법으로 Task3, Task4 가 ThreadC, ThreadD 에서 수행되게 됩니다.

Task 수행 단위는 스레드가 되며, Context Switching을 통해 동시성이 보장되게 됩니다.

다음은 코루틴입니다.

ThreadA 에서 Task1 을 수행하다가,Task2 수행이 필요할 시에 비동기적으로 동일한 ThreadA에서 Task2를 수행합니다.
즉, 동일한 스레드에서 Task1, Task2를 수행할 수 있으므로, Context switching 비용이 발생하지 않습니다.
Task3, Task4도 마찬가지로 ThreadC에서 동시에 수행할 수도 있습니다.
(병렬적(Parallelism)인 것이 아닌 동시성(Concurrency)을 말하는 것입니다.)

즉, 작업 단위가 스레드 단위가 아닌 Object 단위(프로그래머가 단위를 정할 수 있습니다)로 수행되게 됩니다.
동시성 보장 수단이 프로그래머의 코드가 되는 것이죠.

즉, 코루틴을 사용하여 동시성 프로그래밍을 하게되면, 스레드에 비하여 메모리와 Context switching 비용을 절약할 수 있기 때문에 경량 스레드라고 표현하고 있는 것입니다.

이번 시간에는 동시성과 병렬성의 개념과 차이, 그리고 코루틴이란 무엇인지에 대해서 간략하게 알아보았는데요!
다음 시간에는 코루틴을 Python 에서 어떻게 사용하고 구현할 수 있는지 알아보겠습니다.

참조 블로그

이 글을 작성하기에 참조한 블로그는 다음과 같습니다.
Coroutine-위키피디아
Coroutines in Python
파이썬 코딩도장
Coroutine, thread 와의 차이와 그 특징

profile
재빅의 빅데이터 여행 기록 저장소

1개의 댓글

comment-user-thumbnail
2024년 1월 8일

코루틴에 대한 설명 감사합니다

답글 달기