POST

All
Product
Team
Tech
DocVLM: Make Your VLM an Efficient Reader
  • 최윤진
  1. Tech
Python 3.10 신규 문법 : Parenthesized context managers와 PEG Parser
  • S
    seunghoChoe
  1. Tech
UReader: Universal OCR-free Visually-situated Language Understanding with Multimodal Large Language Model
  • 최윤진
  1. Tech
[팀 소개편] KPMG Lighthouse는 어떤 팀인가요?
  • L
    Lighthouse
  1. Team
[챕터 소개편] Backend Chapter를 소개합니다
  • L
    Lighthouse
  1. Team
[챕터 소개편]Frontend Chapter를 소개합니다
  • L
    Lighthouse
  1. Team
[챕터 소개편] AI Chapter를 소개합니다
  • L
    Lighthouse
  1. Team

파이썬의 동시성 관리 : 코루틴(Corutine) 上

Created by
  • L
    Lighthouse
Created at
Category
  1. Tech

0. 들어가기 앞서

지난 시간에는 파이썬의 GIL 제약과 그 제약으로 인해 “찐”효율을 내지 못하는 멀티 스레드 프로그래밍에 대해서 알아보았다.
이번 시간에는 파이썬 비동기의 핵심 키워드 네이티브 코루틴인 Asyncio, async, await를 이해하기 전 이것들의 근간이라 불리는 코루틴(Corutine)에 대해서 알아보자.
들어가기 앞서, 복습 차원에서 지난 시간 내용을 간략하게 정리하자

1. 제네레이터

1.1. 제네레이터

제네레이터는 쉽게 말해서, 여러개의 데이터를 미리 만들어 놓지 않고 필요한 때마다 즉석에서 하나씩 만들어낼 수 있는 객체를 의미한다.
일반적인 함수와 달리 상태를 유지할 수 있다.
즉, 제네레이터는 yield 표현식을 사용하여 값을 반환하고, 다음 호출 시 마지막으로 실행된 yield 표현식 이후부터 실행을 재개한다. 일반함수는 return을 만나면 실행이 끝나버린다. 하지만 제네레이터는 yield 구문에서 “일시정지”의 상태로 값을 외부로 내보낸다. 그 이후에 필요할 때 다시 실행 흐름을 이어나갈 수 있다. 함수 내부에서 사용된 지역 변수등이 메모리에 그대로 유지되어 있기 때문이다.
아래 코드를 살펴보자.
import time def return_abc(): alphabet_list = [] for alphabet in "ABC": time.sleep(1) alphabet_list.append(alphabet) return alphabet_list
함수 return_abc()는, 알파벳을 1초마다 하나하나 리스트에 적재하는 코드이다.
print를 찍어본다면 어떻게 될까?
print(return_abc()) ## 출력값 ['A', 'B', 'C']
너무 당연한 결과이다.
그렇다면 해당 함수를 for loop에 돌려보면 어떻게 될까?
for alphabet in return_abc(): print(alphabet) ## 출력값 ...3초 후 A B C
이 결과 역시 너무나도 쉽게 예상할 수 있다.
여기서 3초라는 시간을 잘 기억해주길 바란다.
위에서 제네레이터는 데이터를 미리 만들어두지 않고, 필요할 때마다 하나씩 만들어내는 객체를 뜻한다 했다.
이것도 코드를 통해 알아보자.
import time def yield_abc(): for alphabet in "ABC": time.sleep(1) yield alphabet
뭔가 위의 코드랑 별로 달라진게 없어보인다.
print를 찍어보자.
print(yield_abc()) ## 출력값 <generator object yield_abc at 0x104fa5620>
!! 예상했던 것과는 달리 제네레이터가 출력 되었다 !!
위에서 언급했던 것처럼 제네레이터는 “필요할 때마다” “하나씩” 만들어 낸다고 했으니, 한번 for문을 통해서 “하나씩” 값을 받아와 보자.
for alphabet in yield_abc(): print(alphabet) ## 출력값 ...1초 후 A ...1초 후 B ...1초 후 C
return_abc() 함수와 비교하였을때, 시간상으로는 똑같이 3초+@가 소요 되었다. 하지만 값을 수령하는 방식에서 차이가 발생했다.
return_abc는 한번에 전부.
yield_abc는 한개씩.
예제코드처럼, ABC 3개의 알파벳이 아닌, 수천 수만개의 알파벳이라면?
yield_abc는 수천~수만초의 시간이 흐르기 전에 값을 계속해서 수령해서 프로세스가 진행 될 테고,
(A 수령 → 작업 → B수령 → 작업 → C수령 → 작업 ………)
return_abc는 수천~수만초의 시간이 흐를때 동안 아무것도 하지 못한체, 하염없이 return_abc의 리턴값만 기다릴 것이다.
(대기…………………………..→ 총 수령 → 총 작업)
또, 메모리 효율 측면에서도 말할게 생기는데 return 키워드를 사용할 때는 모든 결과 값을 메모리에 올려놓아야 하는 반면에, yield 키워드를 사용할 때는 결과 값을 하나하나 메모리에 올려놓기 때문에 메모리 측면에서도 효율 적이다.
( 특히 한번에 메모리에 가득 올리기에 부담이 가는 대용량 파일을 읽을때 효율적일 것 같다 !)
그러한 특성들 때문에 제네레이터는 흔히 게으른 반복자(lazy iterator)라고도 불린다. 이러한 게으른 특성을 잘 활용한다면 효율적인 작업이 가능하다 !
얼떨결에, 장점까지 말해버렸다.

1.2. yield ?

제네레이터도 이해하기 힘든데, 생소한 yield가 등장해버렸다. 어디서 본적이 있지만, 스쳐지나간 적이 많은 키워드라고 생각한다.
보통의 함수는 호출되면 그 내부의 모든 코드를 실행하고 값을 반환한 후 종료되지만, yield를 사용하면 함수는 값을 반환한 후에도 종료되지 않고 일시적으로 멈춘다.
그리고 다음 번에 다시 호출되면 멈췄던 그 지점 코드부터 실행을 재개한다.
⚠️❗⚠️❗⚠️❗⚠️ 선생님 이해가 안돼요❗⚠️❗⚠️❗⚠️❗⚠️❗
이것만 기억하자 !!
yield 키워드는, 코루틴에서 값의 “전달(함수 재개)”과 “반환(함수 중지)”을 모두 담당한다.
값 전달 (input) - 함수 재개
yield를 통해 값을 할당 받는다.
중단되었던 함수라면, 중단됐던 yield의 다음 코드부터 재개 된다.
이때, input값은 일전에 중단되었던 yield 부분에서 할당된다.
값 반환 (output)
yield를 통해 값을 반환한다.
함수가 진행되면서, yield를 만나면 함수를 중단하고 값을 반환한다.
즉 yield는 함수가 일시중지되는 지점을 결정하고, 다시 재개되는 지점을 결정하는 중요한 역할을 한다.

2. 코루틴 (Corutine)

2.1. 코루틴 이란?

코루틴(Corutine)이란, Cooperative + Routine의 의미로, “상호 협력하는 루틴”이라고 할 수 있다.
파이썬의 asyncio / async와 await 키워드가 바로 코루틴을 기반을 둔 비동기 프로그래밍 기법이다.
async - 코루틴 함수 선언.
await - 대기하면서, 다른 루틴이 실행될 수 있도록.
서브루틴과 달리 현재 상태값을 저장하고 메인루틴으로 돌아간 뒤 나중에 호출하게 되면 저장했던 상태를 꺼내서 다음 상태를 진행할 수 있는 함수(루틴)

❓상호 협력

직관적인 이해를 위해, 코드와 그림을 통해 이해해보자 !
def normal_hello(time: str): greeting = "good " return greeting + time + ' Lighthouse' def start_normal_greeting(): print(normal_hello("morning")) print(normal_hello("afternoon")) print(normal_hello("evening")) start_normal_greeting()
## 출력값 good morning Lighthouse good afternoon Lighthouse good evening Lighthouse
팀원들에게 아침, 점심, 저녁 인사를 하는 너무나도 간단한 코드이다.
start_normal_greeting함수 안에서 normal_hello 함수를 호출하고, normal_hello함수가 끝남과 동시에,
normal_hello 함수에 들어있던 모든 것은 사라진다.
이를 보통 메인 루틴에서 서브 루틴을 호출하고, 서브루틴은 종료된다 라고 얘기한다.
아래 그림으로 간단하게 살펴보자.

2.1.1. 일반적인 루틴의 플로우

그림과 같이, 서브루틴은 메인루틴에 의해 호출 및 종료가 결정이되는 **“종속”**관계라고 할 수 있다.
하지만, 코루틴은 방식이 다르다.
위처럼 메인 루틴과 서브 루틴이 **“종속”**적인 관계가 아니라, 서로 **“대등”**한 관계이며, 특정 시점에 상대방의 코드를 실행한다.
아래 예시 코드를 보자.
def coroutine_hello(): greeting = "good " while True: text = (yield greeting) greeting += text def starting_corutine_greeting(): cr = coroutine_hello() next(cr) print(cr.send("morning")) print(cr.send("afternoon")) print(cr.send("evening")) starting_corutine_greeting()
뭔가 코드의 형태도 비슷하고, 출력 값 또한 일치한다 ..! 또 눈에 띄는것이, 아까 설명했던 yield가 등장한다 !
그럼. 해당 코드의 출력 값은 어떻게 될까?
출력값
good morning
good morningafternoon
good morningafternoonevening
원하던 바가 아니다.
그럼 팀원들에게 제대로된 인사를 하기 위해서는 코드를 어떻게 수정해야할까?
def coroutine_hello(): greeting = "good " text = yield while True: text = yield greeting + text + ' Lighthouse' def starting_corutine_greeting(): cr = coroutine_hello() next(cr) print(cr.send("morning")) print(cr.send("afternoon")) print(cr.send("evening")) starting_corutine_greeting()
## 출력값 good morning Lighthouse good afternoon Lighthouse good evening Lighthouse
메인 - 서브 루틴에서의 종속적인 관계와 달리, 코루틴에서는 coroutine_hello 함수가 실행-종료를 반복되는 것이 아니라, 계속해서 “대기”를 하게된다.
print(cr.send("morning")) print(cr.send("afternoon")) print(cr.send("evening"))
print문에서 3번 호출된다 해서
3번 실행 - 3번종료가 아닌,
최초 실행 - 대기 (작업수행) - 종료 라는 것이다 !
다시말해서, 함수의 호출과 종료가 한큐에 일어나는 서브루틴과 달리, 코루틴에서는 함수가 종료될 것 같은 시기에 대기를 하게되니, 다시 호출(사실은 재개)이 된다 하더라도, 변수값이 계속 유지되기 때문이다.
그림으로 보면 다음과 같다.

2.1.2. 코루틴의 플로우

이처럼 코루틴은 함수가 종료되지 않은 상태에서 메인 루틴의 코드를 실행하고, 다시 돌아와 코루틴의 코드를 실행한다. 함수의 코드를 실행하는 지점을 진입점(entry point)라고 하는데, 코루틴은 진입점이 여러개다. (yield) 이런 점에서 코루틴은 일반 루틴에 비해 메인 루틴과의 종속 관계가 아닌 대등한 관계이며, 그래서 상호 협력하는 관계라고 칭한다.

마치며

사실 이번 발표는, 처음 코루틴 발표를 계획했을 때의 30%분량도 채우지 못한 것 같다. 원래 목표는 코루틴에 대한 간단한 설명과 더불어 지난 시간에 발표한 멀티스레딩 프로그래밍과 예시코드로 직접적인 속도비교 및 분석 까지가 목표 였는데, 발표를 위한 공부를 이어가다보니 기반을 닦아 놓지 않으면 이도저도 안될 것 같아서 기초적인 원리에 중점을 둔 발표라고 생각한다.
이번 발표를 간단히 정리해보자면 딱 “코루틴의 개요” 정도일 것 같다.
step1인 개요를 지났으니, 다음 시간에는 좀 더 딥다이브 하게 코루틴에 대해 살펴보고, 코루틴의 발전과정 / async/await로의 진화 과정을 설명 해볼까 한다.
출처