비동기 프로그래밍 1편 - 개념

2024. 3. 5. 19:46Development

안녕하세요, 오늘은 비동기 프로그래밍에 대해 말해보려고 해요.

비동기 프로그래밍은 개발자, 프로세스, 사용자 입장 모두에서 큰 장점을 가져요.

 

우선 개발자는 서비스의 성능을 향상시킬 수 있고, 다양한 흐름으로 로직을 처리할 수 있어 폭 넓은 기능 개발도 가능해요.

그리고 프로세스 입장에서는 자원을 더 효율적으로 사용할 수 있어요. 쉬는 자원을 최대한 사용해서 빠르고 효율적인 처리가 가능하도록 합니다.

마지막으로 사용자 입장에서는 자연스럽고 부드러운 사용자 경험을 만들 수 있습니다. 예를 들어, 스크롤을 할 때 그때 그때 데이터가 로드 되는 것도 비동기 프로그래밍 덕분이라고 할 수 있어요.


1. 파이썬에서의 비동기 프로그래밍

        1 - 1. 코루틴 (Coroutine)

        1 - 2. 태스크 (Task)

        1 - 3. 이벤트 루프 (Eventloop)

2. 비동기 프로그래밍 예제

3. 비동기 프로그래밍 활용 1 - GPT API 비동기 처리하기

4. 비동기 프로그래밍 활용 2 - 복잡한 회원가입 로직 처리하기


1. 파이썬에서의 비동기 프로그래밍 - Asynchronous Programming in Python

비동기(Asynchronization)라는 개념은 여러 작업을 동시에 처리할 수 있도록 하는 프로그래밍 방식입니다.

파이썬에서도 다양한 키워드를 사용해서 비동기 프로그래밍을 할 수 있어요.

 

파이썬에서 반드시 비동기 로직은 비동기 함수 안에서 작성되어야 해요.

 

그리고 비동기 함수 내부 시간이 많이 걸리는 로직도 비동기 방식으로 처리되어야 해요.

그렇지 않으면 내부적으로 *블로킹(Blocking)이 발생하기 때문에 비동기 함수를 쓰는 의미가 퇴색돼요.

 

* 블로킹(Blocking): 어떤 작업을 수행하는 동안 다른 작업을 수행하지 못 하고 대기하는 상태

 

이제 파이썬에서 어떻게 비동기 프로그래밍을 구현할 수 있는지 알아보아요.


1 - 1. 코루틴 (Coroutine)

코루틴은 쉽게 말하면 파이썬에서 정의된 비동기 함수입니다.

파이썬에서 함수는 def 키워드로 정의되는데, 비동기 함수를 정의하려면 async def와 같이 정의하면 돼요.

 

async def func():
    return

print(type(func()))
[Stdout]
<class 'coroutine'>

 

def로 정의된 일반 동기 함수는 func()로 실행할 수 있지만, async def로 정의된 비동기 함수는 func()로 실행할 수 없고, 그저 코루틴이라는 객체 상태기 때문에 따로 실행을 해줘야 돼요.

 

1 - 2. 태스크 (Task)

태스크실행 중인 코루틴이라고 보면 돼요.

즉, async def로 코루틴(비동기 함수) 객체를 만들고, 실행하면 태스크가 돼요.

import asyncio

async def add(a: int, b: int):
    return a + b
    
async def main():
    result = await add(10, 20)
    print(result)

asyncio.run(main())

 

비동기 함수는 기본적으로 로직의 동시 실행을 전제하고 있기 때문에 결과 값이 언제 반환될 지 예측할 수 없어요.

따라서, await 키워드 없이 add(10, 20)만 사용한다면 아래와 같은 에러가 떠요.

RuntimeWarning: coroutine 'add' was never awaited

 

따라서 동기 함수 같이 결과 값을 어떤 변수에 대입하려면 await 키워드를 사용해야 해요.

정리해보면, 비동기 함수는 언제 끝날지 모르기 때문에 await 키워드를 사용해서 함수가 동작이 끝나 반환 값을 줄 때 까지 기다린다고 보면 되겠죠?

 

위에서 설명한 대로라면, await 키워드를 안 쓰면 오류가 나는데, 항상 await 키워드를 사용하여 결과 값을 받을 때까지 기다려야 할까요?

 

파이썬에서는 asyncio 라이브러리의 create_task 함수로 task를 만들고, await을 통해 결과 값을 받을지 받지 않을지 결정할 수 있어요.

import asyncio

async def add(a: int, b: int):
    return a + b
    
async def main():
    task = asyncio.create_task(add(10, 20))
    result = await task
    print(result)

asyncio.run(main())

 

위와 같이 create_task 함수로 task를 만들고, await을 통해 결과 값을 받은 모습이에요.

만약 아래와 같이 create_task 함수로 코루틴을 실행한다면 이 함수는 결과 값과 상관 없이 다른 로직과 동시에 실행할 수 있어요.

import asyncio

async def backgroun_logic():
    return
    
async def main():
    asyncio.create_task(backgroun_logic())

asyncio.run(main())

1 - 3. 이벤트 루프 (Eventloop)

이벤트 루프는 비동기 작업을 실행하고 관리해요.

 

컨텍스트 스위칭(Context Switching)에 대해 들어보셨나요?

알아채기 힘든 매우 빠른 시간에 여러 프로세스나 쓰레드를 교체하면서 실행하며 동시에 실행되는 것처럼 보이는 역할을 해요.

 

비동기 함수의 실행도 실제로 동시에 일어나는 게 아니라, 하나의 쓰레드에서 여러 비동기 함수가 반복하여 실행되면서 동시에 실행되는 것처럼 보이도록 해요.

 

이벤트 루프는 비동기 함수를 실행하는 역할을 하기 때문에, 어떤 순간에 어떤 프로세스가 실행되어야 하는지 관리하는 스케쥴러와 비슷한 성격을 가진다고 보면 돼요.

 

이벤트 루프는 단일 쓰레드에서 실행되기 때문에 동기 작업에 의해 비동기 작업이 가로막히면 다른 쓰레드를 만들어 계속 실행하는 게 아니라, 그대로 동기 작업이 끝날 때까지 기다려요. (블로킹, Blocking)

 

따라서 비동기 함수는 비동기 로직으로 이루어져 있어야 해요.

실행 시간이 0에 가까운 변수 설정, 간단한 함수 실행은 동기적으로 이루어져도 되나, 네트워크 연결, 데이터베이스 쿼리 등 시간이 보다 오래 걸리는 작업은 비동기 방식으로 처리해야 돼요.

 

그렇지 않으면 이벤트 루프는 다른 비동기 함수를 실행시키지 못 하고, 블로킹이 풀릴 때까지 기다려야 해요.


이 정도로 파이썬에서의 비동기 프로그래밍을 하기 위해 알아야하는 간단한 개념 설명을 마칠게요.

 

비동기 프로그래밍은 흐름을 파악하기 다소 어렵고, 복잡해서 단 번에 이해하기 힘들어요.

다음 게시물에는 비동기 프로그래밍 예제와 활용을 통해 비동기 프로그래밍을 더 깊게 이해해보아요.


제 글이 많은 도움이 되었길 바라며,
긴 글 봐주셔서 감사합니다!
멋진 개발자가 되기 위해 더 열심히 달리겠습니다!
- 달맹 -