티스토리 뷰

[event]


이벤트 기반 프로그래밍은 이전에 다룬 한정된 파이썬 어플리케이션과는 다르다.


일반적으로 이러한 우선 프로그램은 플로우를 설정하고 메인 메모리의 실행 관점에서 프로그램의 어떤 부분이


비결정적일지라도 독립적인 접근법을 결정론적으로 따른다.


그림.


이벤트 기반 프로그램에서는 일반적으로 이벤트에 반응하는 일정한 이벤트 루프가 있다.


이벤트 루프가 시작되면, 어떤것이 실행되고 그 순서가 어떻게 되는지 결정하는 시스템상에


필요한 이벤트가 내려진다.


쉬운예로 키보드 입력을 들 수 있다. 


다음에 어떤 키가 눌릴지 결정할 수는 없지만 프로그램이 key-press 이벤트에 반응했을 때 주어지는


키에 대해서 매핑되는 특정 함수를 갖는다.


그림.


이벤트 기반 프로그램이 일반적으로 따르는 흐름 구성을 볼 수 있다.


프로그램이 시작되고 '대기' 상태에 진입하는 것을 볼 수 있다.


이는 특정 이벤트나 시스템 충돌 발생으로 인한 프로그램 종료 때까지 무한정 계속된다.



'대기' 상태는 계속 이벤트를 받아 들이고, 이를 이벤트 핸들러에게 전달한다.


여기서는 특정 이벤트가 하나의 핸들러와 매핑된 것을 보여주며 특정 이벤트 기반 프로그램 에서는


이벤트 및 핸들러의 개수가 이론적으로 무한대일 수도 있다.


이는 운영체제 같은 시스템에 이상적이다. 절차형 방식으로 OS 를 작성해야 한다면,


디자인이 잘된 시스템과 달리 복잡하게 얽힌 코드를 읽기가 매우 어렵다는 사실을 알게 될 것이다.


시스템에서 이벤트 기반 개념을 활용해 전반적인 프로그램의 구조를 단순화하고 기능성을 높이고 디버깅 과정도 줄일수 있다.



[event loop]


모든 이벤트 기반 파이썬 프로그램의 컴포넌트는 이벤트 루프를 갖고 있다.


asyncio 이벤트 루프를 살펴 보자.


이벤트 루프안에서 할 수 있는 일은 다음과 같다.


- 호출등록, 실행, 취소하기

- 하위 프로세스 및 외부 프로그램에서 관련 통신 실행하기

- 스레드 풀에 가장 많이 호출되는 함수 지정하기


모든 이벤트 루프는 필수적으로 주어진 이벤트 타입과 매치된 함수가 연결되기 전에 발생하는 이벤트를 기다려야 한다.


이를 잘 설명하는 예시는 바로 웹 서버다.


수많은 웹 페이지로 구성된 웹 사이트를 제공하는 서버가 있다고 하자.


이벤트 루프는 계속 대기 중일 테고, 요청이 일어나면 관련된 웹 페이지와 연결하게 된다.


웹 서버로부터 생성된 각 요청은 이벤트라고 보면 된다.


이러한 이벤트는 이벤트가 발생할 때 일어나도록 미리 구성해둔 함수와 매치된다.



[asyncio]


ayncio 는 파이썬 3.4 버전에서 소개됏으며, 훌륭한 함수를 제공해 파이썬 커뮤니티에서 많은 인기를 얻고 있다.


asyncio 는 이후에 다뤄볼 코루틴을 활용한 단일 스레드 기반 동시성 프로그램을 쉽게 작성해 주는 모듈이다.


소켓과 그 외 자원들의 멀티플렉싱 I/O 접근 작업을 해주며, 비교적 쉽게 스레드 세이프 프로그램을 


작성할 수 있도록 동기화 작업도 제공해 주고 있다.


이 모듈에는 다음과 같이 다양한 개념들이 있다.


- 이벤트 루프

- 퓨처

- 코루틴

- 태스크

- 트랜스포트

- 프로토콜


이러한 각 개념들은 가독성이 높은 고성능 파이썬 프로그램을 작성하는 데 도움을 준다.


이후에 이러한 개념들에 대해 좀 더 자세히 살펴본다.


asyncio 모듈은 이벤트 기반 방식으로 프로그램을 작성하도록 다양한 툴과 클래스를 제공한다.


https:///docs.python.org/3/library/asyncio.html




[start]


다음 예제에서는 asyncio 이벤트 루프를 반환하는 get_event_loop() 메소드를 활용해 본다.


그런 다음, run_until_complete() 메소드를 활용해 실행하면서 코루틴을 취한다.


코푸틴에 대해서는 이후에 살펴볼 것이며 여기서는 이러한 코루틴이 asyncio 에서 동시성 실행을 디자인할 경우 


필수적인 함수임을 알아두자.


import asyncio


async def coroutine():
print("simple event loop")


def main():
loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine())
loop.close()


if __name__ == '__main__':
main()



[run_until_complete]


run_until_compete() 메소드는 이전에 살펴봤듯이 자체적으로 종료되기 전에 이벤트 루프가 주어진다.


import asyncio


async def foo():
while True:
print("simple event loop")
await asyncio.sleep(1)


def main():
loop = asyncio.get_event_loop()
loop.run_until_complete(foo())
loop.close()


if __name__ == '__main__':
main()

이 코드에서는 이벤트 루프가 시작되고 주어진 함수가 완료될 때까지 foo 함수를 실행한다.




[coroutine]


코루틴은 threading 모듈에서의 Thread 객체와 유사하다.


asyncio 기반 애플리케이션의 코루틴을 활용해 단일 스레드 기반 컨텍스트에서 동작하는 예외를 가진


비동기 프로그램을 작성할 수 있다.


일반적으로 이벤트 기반 프로그램에서 매직이 발생한 부분은 asyncio 모듈에서 중요한 부분이다.


asyncio 기반 프로그램을 살펴보면 이러한 코루틴 객체를 자주 활용했음을 볼 수 있다.


코루틴을 구현하는 데는 다양한 방법이 있는데 가장 먼저 async def 함수를 구현 해야한다.


이것은 파이썬 3.5 버전부터 지원한다.


이함 수를 바탕으로 코루틴 함수를 구현 하는 것을 볼 수 있다.



import asyncio


async def foo():
print("simple event loop")


def main():
loop = asyncio.get_event_loop()
loop.run_until_complete(foo())
loop.close()


if __name__ == '__main__':
main()


두 번쨰 방법은 @asyncio.corotine 데코레이터를 사용한 결합에서 활용하는 것이다.


import asyncio


@asyncio.coroutine
def foo():
print("simple event loop")


def main():
loop = asyncio.get_event_loop()
loop.run_until_complete(foo())
loop.close()


if __name__ == '__main__':
main()


[코루틴 변경하기]


파이썬 시스템에서 높은 성능이 필요할 때 코루틴의 호출을 연결할 수 있다.


공식 문서를 보면 이러한 연결 개념을 잘 설명해 주는 샘플 코드가 있다. 이 코드에는


async def 로 표시된 2개의 코루틴이 있다.


compute코루틴을 바탕으로 1초 동안의 유휴 상태 후 x와 y의 합을 반환한다.


import asyncio


async def compute(x, y):
print(f"compute {x} + {y} ...")
await asyncio.sleep(1)
return x + y


async def print_sum(x, y):
result = compute(x, y)
print(f"{x} + {y} = {result}")


def main():
loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(2, 3))
loop.close()


if __name__ == '__main__':
main()


위 코드를 실행하면 아래와 같이 출력된다.



D:\DevEnv\Python\Python37-32\lib\asyncio\events.py:88: RuntimeWarning: coroutine 'compute' was never awaited

  self._context.run(self._callback, *self._args)



기본적으로 print_sum 함수는 compute 의 함수가 콜백을 기다리지 않으므로 , 프로그램은 결과를 받은것 처럼 


계속하려 했다.


이러한 문제를 해결하고자 await 키워드를 활용해야 한다.


await 키워드는 호출된 코루틴이 겨로가를 반환하기 까지 이벤트 루프의 진행을 막는다.


하지만 이러한 코드의 단점은 비동기성 비동기성의 우수성을 잃어 기본적인 동기적 실행으로


돌아간다는 것이다.


빠르고 쉬운 결정론적 실행을 제공하지만 성능에 영향을 미칠 수 있기에 await 의 사용 필요성을 판단해야 한다.


print_sum 코루틴은 compute 코루틴이 반환한 변수 결과를 인스턴스화 한다.


import asyncio


async def compute(x, y):
print(f"compute {x} + {y} ...")
await asyncio.sleep(1)
return x + y


async def print_sum(x, y):
result = await compute(x, y)
print(f"{x} + {y} = {result}")


def main():
loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(2, 3))
loop.close()


if __name__ == '__main__':
main()


이렇게 동기화 할거면 차리리 아래 처럼 compute 를 비동기 함수가 아니라 일반 동기 함수로 구현하는 것도


고민해 볼만 하다.


import time
import asyncio


def compute(x, y):
print(f"compute {x} + {y} ...")
time.sleep(1)
return x + y


async def print_sum(x, y):
result = compute(x, y)
print(f"{x} + {y} = {result}")


def main():
loop = asyncio.get_event_loop()
loop.run_until_complete(print_sum(2, 3))
loop.close()


if __name__ == '__main__':
main()















































댓글