코루틴은 파이썬에서 다중 작업을 구현하는 또 다른 방법으로, 스레드보다 더 작은 실행 단위이며 리소스 사용량도 적습니다. 이는 각 코루틴이 자체적인 프로세서 상태(컨텍스트)를 가지고 있어, 특정 시점에 한 코루틴을 다른 코루틴으로 전환할 수 있음을 의미합니다. 단지 컨텍스트 저장 및 복원만으로도 실행 흐름이 자연스럽게 유지됩니다.
간단히 말해, 하나의 스레드 내부에서 함수가 임의의 지점에서 현재 상태(임시 변수 등)를 저장하고, 다른 함수로 전환하여 실행할 수 있습니다. 이 과정은 일반적인 함수 호출이 아니라, 개발자가 직접 제어하는 방식으로 이루어지며, 언제 다시 원래 함수로 돌아올지도 명시적으로 결정할 수 있습니다.
간단한 코루틴 구현: yield 사용
yield는 함수 실행 중 일시 중단하고, 이후 다시 이어서 실행될 수 있도록 해줍니다. 이를 활용하면 함수 간의 전환이 가능해집니다.
import time
def dance():
for i in range(5):
print(f'춤을 추고 있습니다... {i}')
yield
time.sleep(0.1)
def sing():
for i in range(5):
print(f'노래를 하고 있습니다... {i}')
yield
time.sleep(0.1)
def run():
dancer = dance()
singer = sing()
for _ in range(5):
next(dancer)
next(singer)
time.sleep(0.1)
if __name__ == '__main__':
run()
greenlet을 이용한 코루틴
greenlet은 yield 기반의 코루틴을 보다 직관적으로 관리할 수 있도록 도와줍니다. 설치 후, 객체 생성 후 switch() 메서드로 실행을 전환할 수 있습니다.
import time
from greenlet import greenlet
def dance():
for i in range(10):
print(f'춤을 추고 있습니다... {i}')
sing.switch()
time.sleep(0.1)
def sing():
for i in range(10):
print(f'노래를 하고 있습니다... {i}')
dance.switch()
time.sleep(0.1)
dance_green = greenlet(dance)
sing_green = greenlet(sing)
def start():
print('춤 시작')
dance_green.switch()
if __name__ == '__main__':
start()
gevent를 통한 자동 전환 코루틴
gevent는 greenlet을 기반으로 하되, 입출력(I/O) 작업(예: 네트워크 요청, 파일 읽기/쓰기 등)을 감지하면 자동으로 다른 코루틴으로 전환합니다. 이는 비동기 처리의 핵심입니다.
설치: pip install gevent
기본 사용법
time.sleep같은 일반적인 지연은gevent.sleep로 대체해야 비동기로 인식됨.spawn로 코루틴 생성,join으로 완료 대기.
import gevent
import time
def dance(n):
for i in range(n):
print(f'춤을 추고 있습니다... {i}')
gevent.sleep(0.5)
def sing(n):
for i in range(n):
print(f'노래를 하고 있습니다... {i}')
gevent.sleep(0.5)
def main():
print('=== 시작 ===')
task1 = gevent.spawn(dance, 5)
task2 = gevent.spawn(sing, 6)
task1.join()
task2.join()
print('=== 종료 ===')
if __name__ == '__main__':
main()
간결한 표현: monkey.patch_all()
monkey.patch_all()를 사용하면, 모든 블록킹 연산(예: time.sleep, socket 등)이 자동으로 gevent 호환 형태로 변경되어, 수동 교체 없이도 비동기 처리가 가능합니다.
import gevent
from gevent import monkey
monkey.patch_all()
def work(name, count):
for i in range(count):
print(f'{name}가 수행 중... {i}')
time.sleep(0.2)
def main():
t1 = gevent.spawn(work, '홍길동', 5)
t2 = gevent.spawn(work, '김철수', 6)
gevent.joinall([t1, t2])
if __name__ == '__main__':
main()