비동기 & asyncio

Sung Jun Jin·2020년 7월 18일
2
post-custom-banner

비동기적(Asynchronous) 처리 방식이란?

일반적인 프로그램의 코드는 특정 작업이 끝나면 다음 작업을 진행하는 순차처리 방식으로 실행된다. 이를 동기적(Synchronous) 처리 방식이라고 한다.

반대로 비동기적(Asynchronous) 처리 방식이란 특정 작업의 종료 일시와 상관 없이 다음 작업을 진행할 수 있다. 즉 A작업이 시작하면 동시에 B작업이 실행되고, A, B 작업은 결과값이 나오는대로 출력된다

When to use

동기처리와 비동기처리 방식의 차이점을 잘 보여주는 그림이 있어 첨부해봤다.

위 설명만 보면 비동기적 처리 방식이 동기적 처리방식보다 더 효율적으로 보이지만 때에 따라서 다르다. 상황에 따라 비동기적 프로그래밍 방식이 더 비효율적일 수도 있다. 아래의 링크는 어느 경우에 비동기적, 동기적 처리 방식을 사용해야 할 지에 대한 개발자들의 의견을 모아놓은 링크다.

When to Use(and Not to Use) Asynchronous Programming : 20 Pros Reveal the Best Use Cases

요약하자면,

비동기식 처리 방식이 효율적일 경우

  • 앱이 맨처음 시작되기 전 데이터에 대한 preloading이 필요한 때
  • 각각의 요청이 서로 독립적인 경우 (많은 양의 요청일수록 더욱 효율적임)
  • Response가 유독 느린 웹사이트의 자원을 가져와야 할 때

비동기식 처리 방식이 비효율적일 경우

  • 간단한 작업을 처리한다거나
  • 로직의 효율성보다는 간결함이 필요할 때
  • DB 커넥션 풀을 세팅하지 않고, 하나의 데이터베이스 서버를 돌리고 있는 상태일 때

예시

파이썬으로 간단한 asynchrounous programming 예시를 만들어보자. 우선 asyncio 모듈을 설치해준다

asyncio란?

asyncio(Asynchronous I/O)는 CPU 작업과 I/O의 병렬처리를 지원하는 파이썬 모듈이다.

pip install asyncio

먼저 asyncio를 사용하려면 코루틴을 생성해야 한다. 코루틴이란 멀티태스킹이 가능한 함수를 의미한다. 코드의 특정 위치에서 실행을 일시 중단하고 다른 코드를 실행할 수 있다. 일반 함수를 사용하면 코드를 한 번만 실행할 수 있지만, 코루틴을 코드를 여러 지점에서 여러번 실행할 수 있다. 이는 자바스크립트의 promise 객체와 비슷하다고 할 수 있다.

코루틴을 생성하기 위해서는 async def 키워드를 사용한다.

async def say_hi():
    print("Hello World")

위에서 정의한 say_hi() 함수를 호출하면 coroutine 객체가 리턴된다

if __name__ == "__main__":
    a = hello()
    print(a)

실행결과

<coroutine object hello at 0x7fa82d6f2560>
sys:1: RuntimeWarning: coroutine 'say_hi' was never awaited

실제로 정의한 coroutine 객체를 실행해보자

import asyncio

async def say_hi():
    print("Hello World")
    
if __name__ == "__main__":
    loop = asyncio.get_event_loop() # 이벤트 루프를 얻음
    loop.run_until_complete(say_hi()) #say_hi 코루틴이 끝날 때 까지 기다림

시간 비교해보기

웹 페이지의 크기를 출력하는 코드를 비동기식, 동기방식으로 처리해 실행시간을 비교해보자

동기식

from time import time
from urllib.request import Request, urlopen

urls = [
    "https://www.google.co.kr/search?q=" + i
    for i in ["apple", "pear", "grape", "pineapple", "orange", "strawberry"]
]

begin = time()
result = []

for url in urls:
    request = Request(url, headers={"User-Agent": "Mozilla/5.0"})
    response = urlopen(request)
    page = response.read()
    result.append(len(page))

print(result)
end = time()
print(f"실행 시간 : {end-begin}")

결과

[437806, 84419, 76639, 97318, 418479, 70884]
실행 시간 : 15.668166875839233

비동기식

import asyncio

from time import time
from urllib.request import Request, urlopen

urls = [
    "https://www.google.co.kr/search?q=" + i
    for i in ["apple", "pear", "grape", "pineapple", "orange", "strawberry"]
]


async def fetch(url):
    request = Request(url, headers={"User-Agent": "Mozilla/5.0"})
    response = await loop.run_in_executor(None, urlopen, request)
    page = await loop.run_in_executor(None, response.read)
    return len(page)


async def main():
	#Task를 ensure_future() 메소드를 사용해 리스트로 만들어준다
    fetches = [asyncio.ensure_future(fetch(url)) for url in urls]
    result = await asyncio.gather(*fetches)


begin = time()
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
end = time()

print(f"실행 시간 : {end-begin}")

결과

[97318, 418459, 77614, 70884, 77901, 437806]
실행 시간 : 4.584640026092529

동기식은 15초, 비동기식은 4초가 걸렸다. 또한 비동기식으로 프로그램을 짤 경우 페이지의 크기 측정이 끝난 순서대로 크기가 출력되는것을 확인할 수 있다.

profile
주니어 개발쟈🤦‍♂️
post-custom-banner

0개의 댓글