본문 바로가기
Programming/Python

[ Python ] 파이썬 비동기 처리 | asyncio | 비동기란 무엇인가 | 비동기 예제

by 코뮤(commu) 2021. 12. 8.
728x90
반응형

 

 

비동기 함수란?

 

비동기 함수에 대해 서술하기 전, 동기 함수의 개념부터 잡고 가야한다.

 

우리가 통상적으로 써왔던 함수가 동기 함수인데,

 

def test():
	print('1')
    print('2')
    print('3')
   
def main():
	test()

main()

 

위 코드를 보면, main 함수는 test를 실행하고,

 

1,2,3 이 순서대로 출력된 이후 종료된다.

main 함수는 test 함수가 종료될 때까지 기다리는 것이다.

 

너무 당연한 말 같다.

 

 

근데 만약, main 이 test 함수가 1,2,3을 출력하는 동안에 처리해야하는 일이 있다면 어떻게 해야할까?

예시는 이렇지만, 만약 엄청나게 많은 데이터를 출력해야하는 상황이라면?

 

마냥 test 함수가 종료되어 정상적으로 return 되기만을 기다리는 것은

프로그램의 성능을 저하시키는 일이 될 수 있다.

 

 

여기서 필요한 것이 비동기 처리이다.

 

 

비동기 함수는 호출된 함수의 실행이 다 끝나지 않았어도 호출한 함수에게 return 하고,

자기 혼자 알아서 백그라운드에서 실행된다.

 

이후에 작업이 다 끝났을 때에야 호출한 함수에게 작업이 끝났다고 통보해주는 것이다.

 

 

동기함수와는 다르게, 비동기 함수는 작업완료를 보장해주지 않는다.

이게 무슨 말인지 조금 풀어서 설명하자면,

동기함수는 main 함수로 제어권이 넘어왔을 시점에는 test 함수가 이미 작업을 완료했다는 보장이 가능하다.

test 함수가 종료해야 main으로 돌아올테니까.

 

그렇지만 비동기 함수에서는 main에 제어권이 있다해도 test함수의 작업이 완료되었는지 아닌지

보장할 길이 없다.

 

 

비동기 처리가 필요한 경우

 

입출력과 관련된 일을 수행해야할 때, 실질적으로 CPU는 대기하고, 다른 장치들이 일하고 있을 것이다.

그때 쉬는 CPU를 착취하려고 할 때, 비동기 처리가 필요하다.

 

결국 CPU의 유휴 시간을 줄여 프로그램의 퍼포먼스를 높이는 것이 비동기 처리의 목적이라고 말할 수 있을 것이다.

 

 

비동기 예제

 

import time

def good_night():
  time.sleep(1)
  print('잘자요')

def main():
  good_night()
  good_night()


print(f"start : {time.strftime('%X')}")
main()
print(f"end : {time.strftime('%X')}")

 

 

 

실행은 간단하게 코랩으로 진행했다.

 

만약 당신에게 매일 잘자라고 말해주는 부모님이 계신다고 가정해보자.

그런데, 잘자라고 말할때 시간이 엄마 100초, 아빠 100 초라면? (물론 예시에서는 1초로 들긴했다)

어차피 다 들어야한다면, 엄마랑 아빠 두 분이 동시에 말하신다면 어떨까?

 

그럼 200초가 아닌 100초 후에 우린 잠들 수 있는 것이다!

 

 

ㅋㅋ...

그냥 바로 비동기로 넘어가겠다.

 

 

 

 

위의 예제 코드를 비동기 함수로 변경해보겠다.

 

 

import time
import asyncio


async def good_night():
    await asyncio.sleep(1)
    print('잘자요')


async def main():
    await asyncio.gather(
        good_night(),
        good_night()

    )
print(f"start : {time.strftime('%X')}")
asyncio.run(main())
print(f"end : {time.strftime('%X')}")

 

 

이것저것 뭐가 많이 생겼다.

 

결과부터 확인해보자.

 

 

 

 

코랩 환경에서 에러가 나서 황급히 파이참으로 옮겼다.

같은 코드인데도 파이참에서는 돌아가지만 코랩에서는 돌아가지 않는다.

 

3분 삽질으로 새로운 것을 알게되었다.

 

 

아무튼, 하나하나 짚어가면서 얘기해보겠다.

 

 

우선 def 키워드 앞에 async 가 새로 생겼다.

 

이렇게 async def 를 이용한 함수를 코루틴이라고 부른다.

아마 파이썬 비동기프로그래밍에 대해 관심이 있으셨던 분들은 코루틴은 한번쯤 들어보셨으리라 생각한다.

 

good_night 함수와 main 함수 앞에 async 키워드를 붙여주면서 비동기 함수가 되었다.

 

good_night 함수는

sleep 을 실행시키는데, 이 sleep 함수는 비동기함수가 아닌 동기함수다.

그렇기 때문에 얘를 asyncio.sleep 이라는 비동기 sleep 함수로 대체해야한다.

asyncio.sleep은 호출되지마자 return 을 하고, background 에서 1초가 지난 이후에 만료되었다 통보한다.

 

 

자, 그럼 sleep 은 비동기처리가 되었다고 하자.

그럼 good_night 함수는 sleep 으로부터 빠져나와 바로 그 아래 print문을 실행하려 할것이다.

 

 

여기서 await 를 짚고 넘어가야한다.

 

await

 

await 는 특정 객체가 끝날 때까지 기다리는 역할을 한다.

 

일단 확실하게 굿나잇 인사는 1초를 소비해야하므로, 끝날때까지 기다릴 수 있게

sleep 앞에 await를 붙여줘야한다.

 

근데, 이렇게 기다리면 비동기 처리를 하는 의미가 뭐임?

이라고 얘기할 수도 있다.

 

await 를 사용하면 비동기 작업이 완료되었다는 통보가 올 때까지 대기하지만,

event loop 를 확인하여 event loop 내 task 가 존재한다면 그 일을 처리한다.

 

자, 그렇게 된다면 우리는 event loop 에 두 개의 good_night 함수를 넣어

첫번째 good_night 함수의 sleep 만료 통보를 기다리는 동안,

두번째 good_night 함수를 처리하면 되는 것이다.

 

asyncio.gather() 를 이용하여 await 키워드가 붙은 good_night() 을 event loop 에 등록한다.

 

 

 

이후 asyncio.run() 함수를 통해 비동기 함수를 호출하면 된다.

 

일반적으로 우리가 동기함수를 호출하듯 함수를 호출해버리면,

아래와 같은 에러를 볼 수 있을 것이다.

 

 

 

 

 

RuntimeWarning: coroutine 'main' was never awaited

 

이 asyncio.run 함수를 통해 asyncio 이벤트 루프를 관리할 수 있게 되는 것이다.

얘가 새로운 event loop를 생성하고 닫는 역할을 해주기 때문에,

프로그램의 메인 진입점으로 사용해야하며, 딱 한번만 호출시킬 수 있다.

 

 

 

 

이번 포스팅에서는 파이썬의 비동기 프로그래밍에 대해 다뤄봤다.

현재 진행하는 프로젝트에 비동기처리가 들어가야할 것 같아 드디어 비동기 처리를 공부해보는 시간이었다.

 

 

 

728x90
반응형