초보 개발자

django 비동기 이해하기, generator 본문

AI 웹개발 트랙 - 내배캠/9주차 ~

django 비동기 이해하기, generator

taehyeki 2022. 2. 27. 23:41

2021.12.16 - [Python/윤성우 열혈 파이썬] - 9강 제너레이터 함수

 

9강 제너레이터 함수

[제너레이터에 대한 이해와 제너레이터 함수] 이번에 소개하는 제너레이터는 iterator 객체의 한 종류이다. 때문에 제너레이터를 전달하면서 next함수를 호출하면 값을 하나씩 얻을 수 있다. 제너

taehyeki.tistory.com

generator란 전에 윤성우 열혈파이썬에서 배운내용을 정리한 적이 있다.

 

장고 심화실무 강의에서 비동기에 설명을 할 때 generator를 먼저 설명해주셨다. 이유는 generator객체를 사용하면, 함수가 살아있는 상태?가 되므로, 따로 바깥에 변수를 지정해두지 않아도 함수안에 local변수를 지정해서 그걸 while True: 무한반복으로 로컬 변수를 yeild하는 방식으로 대체할 수 있기 때문이다.

기존 룰렛함수 last_coupons라는 데큐를 만들어서 거기에 저장하는 방식

from collections import deque
import random


COUPONS = (500, 1000, 2000, 3000, 10000)
COUPONS_WITHOUT_500 = (1000, 2000, 3000, 10000)


def rullet(last_coupons):
    picked = random.choice(COUPONS)
    if picked == 500 and last_coupons.count(500) == 2:
        picked = random.choice(COUPONS_WITHOUT_500)  # 3연속 500원 금지!
    last_coupons.append(picked)
    return picked


# client 코드 (rullet()을 호출하는 코드)
last_coupons = deque(maxlen=2)
while True:
    picked = rullet(last_coupons)
    print(picked)

새로운 룰렛함수 제네레이터를 만들어 함수를 일시정지 시켜서 활용하는 방법

from collections import deque
import random


COUPONS = (500, 1000, 2000, 3000, 10000)
COUPONS_WITHOUT_500 = (1000, 2000, 3000, 10000)


def rullet_gen():
    last_coupons = deque(maxlen=2)  # 함수 local 변수
    while True:
        picked = random.choice(COUPONS)
        if picked == 500 and last_coupons.count(500) == 2:
            picked = random.choice(COUPONS_WITHOUT_500)  #  3연속 500원 금지!
        last_coupons.append(picked)
        yield picked


# client 코드
for coupon in rullet_gen():  # 호출하는 쪽에서는 이렇게나 쉽게 사용할 수 있답니다!
    print(coupon)

이 것이 async와 비슷한 점이 많기 때문에 설명을 해주셨다고 한다.

 

그리고 chess exhibition과도 비슷하다고 한다.

chess exhibition이란 한명의 체스마스터가 도전자들과 겨루는 방식이다. 체스마스터는 자신의 수를 두고, 도전자들이 어떤 수를 둘지 기다리지 않고 바로 다음 도전자의 체스판으로 넘어간 뒤 자신의 수를 두고 바로 다음 도전자에게로 넘어가는 방식이다.

마치 체스마스터를 쓰레드, 수를 두는 것을 네트워크 요청, 도전자가 고민하는 것을 다른 머신의 테스크 수행에 빗대어표현하였다. 이 용어는 잘 모르기에 따로 공부해보아야겠다.

 

함수에 yield가 들어가면 제너레이터 함수가 되는 것처럼 함수정의가 async def로 시작하면 코루틴이 된다고 한다.

 

async가 붙은 함수를 호출하면 함수가 실행되는 대신 코루틴 객체가 생성이 된다고 한다. 제너레이터함수를 실행하면 실행되는거 개딘 제너레이터 객체가 생성되는 것 처럼 말이다.

 

async 안에서는 await을 사용할 수 있다고 한다. 제너레이터 함수 안에서 yield를 사용하는 것과 마찬가지.

함수 실행 중 await을 만나면 다음과 같은 일이 일어난다고 한다.

1. await문의 코루틴을 event loop에 등록한다.

2. 실행 컨텍스트가 이벤트 루프 안의 다른 코루틴 객체에게 넘어간다. 제네레이터가 yield를 만나면 반환하고 멈추는 것 처럼

 

이벤트 루프에 등록했던 코루틴이 실행 완료되면, 다시 실행을 재개한다. 제네레이터가 일시정지 되었다 다시 실행되는 것 처럼

 

이벤트 루프는 루프안의 코루틴을 차례로 실행한다.

 

어떤 조건에 의해 await이 끝나면 await이후 문장부터 다시 실행한다.

 

이벤트 루프는 코루틴을 등록받고 실행하는 역할을 한다. 

 

 

--------------------------------------------강의 내용이 어려워서 따로 찾아 보았다.

js 기준

콜백의 동기와 비동기

콜백 함수: 나중에 실행하는 함수

 

동기적 콜백, 비동기적 콜백

fakeSetTimeout을 만들어보자, 

 

동기적은 순차적인 실행을 뜻한다. 즉 위에서 부터 코드가 차례로 하나가 끝나면 다음 걸 실행하고 또 끝나면 다음걸 실행하는 것을 뜻한다 앞의 것이 끝나야만 그 다음줄로 넘어가는 것이다. 

 

아래의 함수는 콜백함수를 전달하지만 동기적으로 진행이 된다. 

function fakeSetTimeout(callback, delay){
 callback();
 }
 
 
 console.log(0);
 
 fakeSetTimeout(function(){
  console.log('hello');
  }, 0);
  
   console.log(1);
   
   
   
   -----------
   0
   hello
   1

 

이번엔 비동기적 콜백을 만들어보자 진짜 setTimeout을사용하면 된다

 console.log(0);
 
setTimeout(function(){
  console.log('hello');
  }, 0);
  
  console.log(1);
   
   
   
   -----------
   0
   1
   hello

동기적 콜백과, 비동기적 콜백을 비교하였을 때 출력순서가 다른 것을 확인할 수 있다. 심지어 딜레이가 0인데도 말이다.

그 이유는 콜백함수를 비동기적으로 실행하기 때문이다.

 

timer는 js밖에 존재한다. 브라우저에서 제공한다.setTimeout을 실행하면 콜백함수를 타이머에게 알려주고 0초뒤에 이 함수를 실행해줘라고 한다. 근데 자세히 말하면 0초 뒤에 실행해줘가 아니라, 실행 시점으로 부터 0초뒤에 callback que에 넣어줘라는 말이다. 콜백 que는 호출 스택이 비어있을 때 (메인 함수가 다 끝나서야) 비로소 하나 하나씩 호출 스택으로 넘어가게 된다.

 

따라서 console.log(0)이 실행된 후 0을 출력하고 호출스택 에서사라지고, 

setTimeout이 실행되고 callback que에 callback 함수를 넣고 호출스택에서 사라진다. 

그리고 console.log(1)이 실행되고 1을 출력하고 호출스택에서 사라진다. main이 끝나면

callback que에 있던 callback함수가 실행이 되어 console.log(hello)을 실행, hello를 출력하고 호출스택에서 사라진다.

 

콜백은 나중에 실행하라고 넘겨주는 것이지만, 콜백함수를 받는 함수가 어떻게 동작하느냐에 따라서 동기적일 수도 있고 비동기적일 수도 있다. 단순 자바스크립트 코드라면(내부 연산만 되는 것, fakeSetTimeout) 동기적으로 실행이되고 외부(서버 에서 가져온다거나, 타이머를 가져온다거나 )에 있는 무언가에 콜백을 넘긴다면 그것은 바로 호출스택(call stack)에서 실행되는 것이 아니라 que에 들어가게 된다. 일례로 버튼에 이벤트함수를 넣게 되면 돔에다가 넣게 되는 것이고 DOM은 자바스크립트 내에 있는 것이 아니라 타이머처럼 외부에 있다. 돔에 있는 버튼에다가 콜백을 연결시켜준 것이고, 버튼에서 클릭하게 콜백함수를 집어 넣는 곳이 바로 que(callback que)이다. 따라서 콜백을 받은 애가 콜백함수를 큐에 넣냐, 콜스택에 넣냐에따라서 동기냐 비동기냐로 나눌 수 있다.

 

 

이벤트루프는 호출스택과 콜밸 큐를 계속 주시하고 있다.

호출 스택에 비어있으면 먼저 들어온 순서대로 콜백 큐에 있는 콜백 함수들을 호출 스택으로 집어 넣는다.

-------------------------------------

코루틴 사용하기

 

메인 루틴과 서브 루틴( A함수 안에 B함수가 있다면 B함수가 실행이 된 뒤 다시 A함수로 돌아옴 그리고 B함수의 내용은 다 사라짐 )처럼 종속된 관계가 아니라 서로 대등한 관계이며 특정 시점에 상대방의 코드를 실행한다.

 

코루틴은 함수가 종료되지 않은 상태에서 메인 루틴의 코드를 실행한 뒤 다시 돌아와서 코루틴의 코드를 실행한다.

코루틴이 종료되지 않았으므로 코루틴의 내용도 계속 유지된다.

일반 함수를 호출하면 코드를 한 번만 실행할 수 있지만 코루틴은 코드를 여러 번 실행할 수 있다.