Skip to content

2.19. 내용정리: 22일차

흔한 찐따 edited this page Apr 15, 2022 · 2 revisions

코루틴 (Coroutine)

  • 코루틴이란, cooperative routine 의 약어이며, 서로 협력하는 루틴이라는 의미이다.
  • 코루틴은 메인 루틴과 서브 루틴처럼 종속된 관계가 아니라 서로 대등한 관계이다.
  • 코루틴은 특정 시점에 상대방의 코드를 실행한다.
  • 즉, 함수의 호출이 끝날때까지 계속 대기하는 것이 아니라, 대기하는 동안에 다른 일을 처리한다.

루틴 (Routine)

  • 루틴이란, 어떤 작업을 정의한 명령어의 집합을 의미한다.
  • 즉, 함수, 모듈 등이 루틴이 되는 것이다.
  • 메인 루틴(main routine)은 작업 흐름의 기본이 되는 루틴이다.
  • 서브 루틴(sub routine)은 메인 루틴의 흐름에 종속된 루틴을 의미한다.
  • 주로 서브 루틴을 함수라고 표현한다.

파이썬에서의 코루틴

파이썬에서는 코루틴은 크게 두 가지로 분류된다.

  1. 일반적인 코루틴 (제너레이터 함수의 특별한 형태)
  2. async 키워드와 await 로 호출이 가능한 함수

파이썬 공식 문서의 용어집에서 코루틴 함수 문서에서는 다음과 같이 정의하고 있다.

  • 코루틴 객체를 돌려주는 함수.
  • 코루틴 함수는 async def 문으로 정의될 수 있다.
  • 또한 awaitasync forasync with 키워드를 포함할 수 있다.

일반적인 코루틴

  • 제너레이터는 yield 키워드를 통해 값을 생성한다.
  • 코루틴으로 만들 경우, 변수 =(yield) 와 같은 형태로 정의하여 외부로부터 값을 얻을 수 있다.
  • next 함수를 통해 코루틴 안의 코드를 처음 실행시켜서 yield 까지 코드를 수행한다.
  • 정의된 코루틴으로 생성된 코루틴 객체는 send 메서드를 갖는다.
  • send 메서드를 통해 코루틴으로 값을 보낼 수 있다.

예시

아래는 간단한 코루틴을 정의한 예시이다.

def coroutine():
    while True:
        # 함수 외부에서 값을 받아온다.
        # 값을 받아오려면 '변수명 = (yield)' 와 같이 선언한다.
        x = (yield)
        print('받아온 값:', x)


cor = coroutine()
# 제너레이터를 사용할 때 'next' 함수를 통해 다음 값을 불러왔다.
# 코루틴은 'next' 함수를 통해 초기화 작업을 진행한다.
next(cor)

# 'send' 메서드를 통해 값을 보낸다.
cor.send(10)
cor.send('안녕하세요.')
cor.send('흔한 찐따입니다.')
cor.send(3.14)

결과

받아온 값: 10
받아온 값: 안녕하세요.
받아온 값: 흔한 찐따입니다.
받아온 값: 3.14
  • 위의 예시에서 살펴볼 수 있듯, 코루틴 함수에 값을 보내는 것이 가능하다.
  • 또한, 코루틴을 초기화 할때, next 함수를 사용하지 않고 send 메서드에 None 값을 넘겨주어도 같은 결과를 낼 수 있다.
cor = coroutine()
cor.send(None)
cor.send(10)

다시 아래의 예시를 살펴보자.

def coroutine():
    y = 0
    while True:
        x = (yield y)
        print('코루틴 호출:', x)
        y += x


cor = coroutine()
cor.send(None)

# '1'가 출력된다.
x = cor.send(1)
print('x:', x)

# '3'이 출력된다.
x = cor.send(2)
print('x:', x)

# '6'가 출력된다.
x = cor.send(3)
print('x:', x)

# '10'이 출력된다.
x = cor.send(4)
print('x:', x)

결과

코루틴 호출: 1
x: 1
코루틴 호출: 2
x: 3
코루틴 호출: 3
x: 6
코루틴 호출: 4
x: 10

정리

위의 예시로부터 알 수 있는 사실은 다음과 같다.

  1. 메인 루틴은 코루틴을 호출하는 부분이다.
    • 즉, 위의 예시에서의 코루틴 외부에 해당한다.
  2. 코루틴은 서브 루틴이다.
    • 위의 예시에서도 알 수 있듯, 코루틴은 메인 루틴의 작업을 위해 존재한다.
  3. 그러나 메인 루틴과 서브 루틴이 동등한 관계이다.
    • 실행 결과를 보면 알 수 있듯, 메인 루틴이 실행되고 그 후에 코루틴이 실행된다.
  4. 메인 루틴과 코루틴은 "티키타카(?)" 하는 관계이다.
    • 실행 결과를 보면 알 수 있듯, 호출되는 순서와 그로 인한 반환값이 "티키타카(?)" 되고 있음을 알 수 있다.
    • 위의 결과를 확인해보면 코루틴 호출이 종료된 후에 y 값이 다시 0 으로 초기화되지 않고 제너레이터처럼 계속 유지되고 있음을 확인할 수 있다.
    • 이는 달리 말하자면, 제너레이터는 코루틴의 일반적인 형태라고도 볼 수 있다.

네이티브 코루틴 (Native Coroutine)

  • 파이썬 표준 라이브러리인 asyncio 을 사용해서 코루틴을 만들 수 있다.
  • asyncio 라이브러리는 Asynchronous I/O의 약어로, 비동기 프로그래밍을 위한 모듈이다.
  • asyncio 라이브러리를 통해 CPU 작업과 I/O를 병렬로 처리가 가능하도록 해준다.
  • 이때, asyncio 라이브러리로 만들어진 코루틴을 일반 코루틴과 구분하기 위해 네이티브 코루틴이라고 한다.
  • 이렇게 만들어진 네이티브 코루틴은 async 키워드와 await 키워드로 네이티브 코루틴을 만들거나 실행시킬 수 있다.

asyncio

파이썬 공식 문서의 asyncio 문서에서는 다음과 같이 설명하고 있다.

  • asyncioasyncawait 구문을 사용하여 동시성 코드를 작성하는 라이브러리이다.
  • asyncio 는 고성능 네트워크 및 웹 서버, 데이터베이스 연결 라이브러리, 분산 작업 큐 등을 제공하는 여러 파이썬 비동기 프레임워크의 기반으로 사용된다.
  • asyncio 는 종종 IO 병목이면서 고수준의 구조화된 네트워크 코드에 가장 적합하다.
  • asyncio 는 다음과 같은 작업을 위한 고수준 API 집합을 제공한다.
    • asyncawait 구문: 파이썬 코루틴들을 동시에 실행하고 실행을 완전히 제어할 수 있다.
    • asyncio.stream : 네트워크 IO와 IPC를 수행한다.
    • asyncio.subprocess : 자식 프로세스를 제어한다.
    • asyncio.queues : 큐를 통해 작업을 분산한다.
    • asyncio.locks : 동시성 코드를 동기화한다.

동기(Synchronous)와 비동기(Asynchronous)

  • 동기 처리 방식은 어떤 루틴이 끝나면 다음 루틴을 처리하는 순차처리 방식을 의미한다.
  • 비동기 처리 방식은 여러 루틴을 처리하도록 미리 예약한 뒤 해당 루틴이 끝나면 결과를 받는 방식을 의미한다.

즉, 코루틴은 비동기 처리 방식의 루틴임을 의미한다.

async

  • async 키워드는 파이썬 3.5 버전부터 지원하는 키워드이다.
  • async 키워드는 단독으로 사용되지 않으며, 다른 키워드와 함께 사용된다.
  • 예를 들어, 네이티브 코루틴을 만들기 위해서는 아래와 같이 async def 키워드를 사용한다.
async def native_coroutine():
    ...

예시

아래의 예시는 asyncio 라이브러리를 사용해 간단한 네이티브 코루틴을 만드는 예시이다.

import asyncio


async def native_coroutine():
    print('네이티브 코루틴 호출')


loop = asyncio.get_event_loop()
loop.run_until_complete(native_coroutine())
loop.close()

결과

네이티브 코루틴 호출

위의 예시를 확인해보면 get_event_loop 함수와 run_until_complete 함수를 통해 네이티브 코루틴을 불러오고 실행하는 것을 알 수 있다.

파이썬 공식 문서에서의 이벤트 루프에서는 다음과 같이 정의하고 있다.

  • 이벤트 루프는 모든 asyncio 응용 프로그램의 핵심이다.
  • 이벤트 루프는 비동기 태스크(task) 및 콜백을 실행하고 네트워크 IO 연산을 수행하며 자식 프로세스를 실행한다.
  • 응용 프로그램 개발자는 일반적으로 asyncio.run() 과 같은 고수준의 asyncio 함수를 사용해야 한다.
  • 루프 객체를 참조하거나 메서드를 호출할 필요가 거의 없다.
  • 현재의 이벤트 루프를 가져오려면 get_event_loop 함수를 통해 불러올 수 있다.
    • 현재 OS 스레드에 현재 이벤트 루프가 설정되어 있지 않고, OS 스레드가 메인이고, set_event_loop() 가 아직 호출되지 않았으면, asyncio 는 새 이벤트 루프를 만들어 현재 이벤트 루프로 설정한다.
    • 이 함수는 (특히 사용자 정의 이벤트 루프 정책을 사용할 때) 다소 복잡한 동작을 하므로, 코루틴과 콜백에서 get_event_loop() 보다 get_running_loop() 함수를 사용하는 것이 좋다.
    • 새로운 이벤트 루프를 만들려면 new_event_loop() 함수를 통해 만들 수 있다.
    • 저수준 함수를 사용하여 수동으로 이벤트 루프를 만들고 닫는 대신 asyncio.run() 함수를 사용하는 것도 고려해야 한다.
  • 가져온 이벤트 루프가 끝날 때까지 계속 기다리는 run_until_complete 함수를 통해 네이티브 코루틴을 실행시킨다.
  • 이벤트 루프를 닫으려면 close 메서드를 사용한다.

await

  • await 키워드 역시 파이썬 3.5 버전부터 지원하는 키워드이다.
  • await 키워드는 어웨이터블(awaitable)에서 코루틴 의 실행을 일시 중지한다.
    • 어웨이터블await 표현식에 사용할 수 있는 객체를 의미한다.
    • 코루틴이나 __await__() 메서드를 가진 객체가 될 수 있다.
  • await 키워드는 오직 코루틴 함수 에서만 사용할 수 있다.
  • 이벤트 루프를 얻어온 후에 run_until_complete 함수를 통해 실행시켰지만, await 키워드를 통해 네이티브 코루틴을 실행시킬 수 있다.
  • await 키워드는 코루틴 객체, 퓨처( Future ) 객체, 태스크 객체를 지정하면 해당 객체가 끝날 때까지 기다린 뒤 결과를 반환한다.

예시

아래의 예시는 네이티브 코루틴에서 await 키워드를 사용하는 예시이다.

import asyncio


async def f(x):
    y = x ** 2
    return y

async def g(x):
    y = await f(x)
    print(y)


x = 10
loop = asyncio.new_event_loop()
loop.run_until_complete(g(10))
loop.close()

결과

100

위의 예시에서도 확인할 수 있듯, await 키워드를 통해서 async def 키워드를 통해 만들어진 네이티브 코루틴을 호출할 수 있다.

흔한 찐따

안녕하세요, 흔한 찐따 입니다.

Clone this wiki locally