Python

파이썬 동시성 프로그래밍 - (9) 제네레이터 & 코루틴 & asyncio & async/await

[하마] 이승현 (wowlsh93@gmail.com) 2017. 4. 30. 20:44

연재순서

1. threading

2. Condition & Semaphore

3. Queue

4. multiprocessing

5. 비동기 (gevent) 

6. 분산 (celery)

7. GPGPU (PyCUDA)
8. concurrent.future

9. 코루틴,asyncio,async/awit



제네레이터,코루틴,네이티브 코루틴과
 ASYNC/AWAIT [번역]




참고 : 이 게시물은 주로 Python 3.4 에서 소개 된 기능에 대해 설명하며, 네이티브 코루틴과 async / await 구문은 Python 3.5에서 제공됩니다. 따라서 파이썬 3.5 이상을 사용하여 코드를 시험해 보는 것이 좋을 것 입니다.



제네레이터


제네레이터는 말그래도 값을 생성하는 함수입니다. 알다시피 함수는 값을 반환한 다음 자신의 스코프를 소멸시키고 , 다시 함수를 호출하면, 처음부터 다시 시작됩니다. 즉 한 번 실행됩니다. 그러나 제네레이터 함수는 값을 생성하고 함수의 실행을 일시 중지 할 수 있습니다. 컨트롤이 호출 스코프로 반환되며 ,원하는 경우 실행을 다시 시작하여 다른 값 (있는 경우)을 얻을 수 있습니다. 이 예제를 보시죠.

제네레이터 함수는 어떤 값도 직접 반환하지 않고, 호출되면 반복자와 같은 제네레이터 객체를 건네줍니다. 그 후 제네레이터 객체에 대해 next ()를 호출하여 값을 반복 할 수 있습니다. 또는 for 루프를 실행하여 처리합니다.


그렇다면 제네레이터는 어떨때  유용할까요? 독자분의 상사가 최대 100 개의 숫자 시퀀스를 생성하는 함수를 작성하도록 요청했다고 가정 해 봅시다 ( range() 의 단순한 간략화 된 버전). 빈 리스트를 가져 와서 번호를 계속 추가 한 다음 번호가 있는 리스트을 반환하는 방식을 택했습니다. 그러나 요구 사항이 변경되고 최대 1억개의 숫자가 생성 되어야 하는것으로 변경 한다고 할 때. 이 번호를 리스트에 저장하면 곧 메모리가 부족해질 것입니다. 그런 상황에서 제네레이터가 활약하는데요. 즉 이 번호는 리스트에 모두 저장하지 않고 생성 할 수 있습니다. 다음과 같이 해봅시다.


굳이 메모리에 적재해두지 않고서도 원하는 만큼 번호를 출력 할 수 있으며, 위에서는  번호 9 이상을 출력하지 않습니다만, 함수 컨텍스트를 다시 시작하면 다시 시작됩니다.


요약 : 제네레이터 함수는 하나의 값을 반환하는 대신 실행을 일시 중지하고 여러 값을 생성 할 수있는 함수입니다. 또한 호출되면 iterable처럼 작동하는 제네레이터 객체를 제공하며 반복적으로 값을 얻을 수 있습니다.


코루틴

이제 제네레이터를 사용하여 함수 컨텍스트에서 데이터를 가져올 수 있고 실행을 일시 중지 할 수 있음을 알게 되었습니다. 근데 제네레이터에 데이터를 푸시하고 싶다면 어떻게 해야 할까요? 즉 제네레이터 자신이 계속 데이터를 만드는 것이 아니라, 외부에서 제공되는 데이터를 소비하는 역할을 하고 싶습니다. 이때 코루틴이 등장 할 때입니다. 값을 받는데 사용하는 yield 키워드는 함수 내부의 "=" 오른쪽에 있는 표현식으로 사용 할 수도 있습니다. 제네레이터 객체에 대해 send() 메서드를 사용하여 값을 함수로 다시 전달할 수 있는데요. 이를 "제네레이터 기반 코루틴" 이라고 합니다. 아래는 그 예입니다.

(역주: 함수와 제네레이터 , 코루틴 모두 def 를 통해 만듭니다. 이것에 대해 모두 다르게 표시해야 한다고 주장하는 사람들이 있으며, 파이썬의 창조자 귀도는 이에 반대하여 def 로 통일되어 있습니다. 그리고 코루틴은 반복이 목적이 아닙니다. 반복이 목적인 것은 분명히 제네레이터의 역할입니다. 코루틴은 외부와의 상호 작용입니다.

소스를 살펴보시면 먼저 next () 함수를 사용하여 평소와 같이 값을 가져옵니다. 이것은 "Hello"를 얻습니다. 그런 다음 send () 메소드를 사용하여 값을 제네레이터에 보냅니다. 함수를 다시 시작하고 우리가 hello에 보낸 값을 할당하고 다음 줄로 이동하여 명령문을 실행합니다. 결국 우리는 send () 메소드의 반환 값으로 "World"를 얻습니다. 먼가 한바퀴 돈 느낌이네요. 


우리가 제네레이터 기반 코루틴을 사용할 때 "제네레이터"와 "코루틴"이라는 용어는 일반적으로 같은 의미입니다. 그것들은 정확히 똑같은 것은 아니지만, 많은 경우에 종종 혼용됩니다. 그러나 Python 3.5에서는 네이티브 코루틴과 함께 async/await키워드가 주로 언급됩니다. 이 게시물의 후반부에서 논의 될 것 입니다.


파이썬 코루틴에 대한 개념은 요기 참고 -> Haerakai's Lab(파이썬의 코루틴)

Async I/O and the asyncio module

Python 3.4부터 일반적인 비동기 프로그래밍을 위한 멋진 API를 제공하는 새로운 asyncio 모듈이 생겼습니다. 우리는 asyncio 모듈과 함께 coroutines를 사용하여 async io를 쉽게 수행 할 수 있게 됬는데요. 다음은 공식 문서의 예입니다.

(
역주: 전체 시스템이 블러킹이 안되게 하는 방법으로 첫째, 멀티쓰레드를 통해 하나만 블럭되게 한다 2. 비동기 방식을 사용한다.  이렇게 2개로 크게 볼 수 있습니다. 네 맞습니다. asycnio 는 비동기 방식에 대한 이야기 입니다. 자바스크립트가 그러하듯이~

위의 코드 역시 스스로를 잘 설명해 주고 있습니다. 이 함수(display_date) 는 주어진 시간(초) 후에 완료되는 코루틴 (coroutine)입니다. 식별자 수(num)와 이벤트 루프(loop)를 매개변수로 받아 현재 시간을 계속 출력하는 coroutine 인 display_date (num, loop) 을 만듭니다. 코루틴이기 때문에 외부로 부터 값을 받아드리는 성질이 있다는 것은 예상 할 수 있겠지요?  즉 다음 asyncio.sleep () 함수 호출의 결과를 기다리기 위해 키워드yield from 를 사용했습니다. 그래서 우리는 그것에 임의의 초를 보내고 asyncio.ensure_future()를 사용하여 기본 이벤트 루프에서 코루틴의 실행을 스케쥴합니다. 그런 다음 루프가 계속 실행되도록 요청 합니다.

출력을 보면 두 개의 coroutine이 동시에 실행되는데요. yield from 를 사용할 때, 이벤트 루프는 코루틴의 실행을 일시 중지하고 다른 루틴을 실행합니다. 따라서 두 개의 코루틴이 동시에 실행됩니다 (그러나 잊지 말아야 할 것은 이벤트 루프가 단일 스레드이기 때문에 병렬로 실행되지 않습니다).

 yield from 는for x in asyncio.sleep(random.randint(0, 5)): yield x 에 대한 멋진 syntactic sugar 입니다. 그것은 비동기 코드를 좀 더 간략히 만들어 주죠. 

Native Coroutines and async/await

파이썬 3.5에서 우리는 async / await 구문을 사용하는 새로운 네이티브 coroutine 을 사용합니다. 이전 함수는 다음과 같이 작성 할 수 있습니다.

강조 표시된 선(노랑)을 살펴보십시오. def 키워드 앞에 async 키워드를 사용하여 네이티브 코루틴을 정의하고 있으며 네이티브 코루틴에서는 yield 대신 await 키워드를 사용합니다.



Native vs Generator Based Coroutines: Interoperability

구문의 차이점을 제외하고는 네이티브 와 제네레이터 기반 코루틴간에 기능상의 차이점은 없습니다. 구문을 혼용하는 것은 허용되지 않습니다. 그래서 제네레이터 기반 코루틴에서  await   혹은 네이티브 코루틴 내부에서 yield/yield from을 사용할 수 없습니다.


이런 차이점에도 불구하고 이들 간의 상호 운영이 가능합니다. 우리는 오래된 제네레이터 기반의 것들에
@ types.coroutine 데코레이터를 추가하기만 하면 되는데요. 그런 다음 다른 유형의 내부에서 사용할 수 있습니다. 네이티브 코루틴 (coroutine) 내부의 제네레이터 기반 코루틴을 통해
await  할 수 있으며, 제네레이터 기반 코루틴안에서 네이티브 코루틴으로 부터 yield from  할 수 있습니다. 다음은 예입니다.








번역:

 http://masnun.com/2015/11/13/python-generators-coroutines-native-coroutines-and-async-await.html