티스토리 뷰

파이썬 공부를 하다보면 어느순간부터 기본적인 내용이 아닌, 보다 심층적인 내용을 공부해야겠다고 느낄때가 있다.

특히, 프로그래밍을 하다보면, 속도가 가장 큰 이슈로 발생하는데, 필자는 정형데이터를 전처리하는 과정에서 속도측면을 개선해보고자 '비동기 방식'을 접하게 되었다.

 

일단, 상식적으로 병렬적으로 처리한다는 내용은 누구나 다 알것이지만, 막상 코드로 작성하려고 하니, 어렵다.

그래서 이 포스팅을 통해 정리하고자 한다.

 

누구나 쉽게 접할 수 있도록, jupyter notebook의 노트북 형식( 셀 실행 ) 으로 진행하고자 한다.

 

일단,  노트북형태로 작업을 하려면, 아래와 같은 환경 설정이 필요하다.

!pip install nest_asyncio
import nest_asyncio
nest_asyncio.apply()

노트북 형식은 이벤트 루프가 이미 실행되고 있는 상태라 위와같은 설정을 해줘야 한다고 한다,

 

본격적으로 하나하나씩 예제를 살펴보면서 진행하고자 한다.

import asyncio

async def hello(first, second):
    print(first)
    await asyncio.sleep(1)
    print(second)

asyncio.run(hello("ww","22"))
# ww
# 22

여기 간단한 예제를 하나 작성했다.

 

async로 함수를 작성하면, 해당 함수는 비동기식으로 작동하게 된다.

물론, 이 함수를 실행하고자 할때는 asyncio.run(함수())를 통해 새로운 이벤트 루프를 발생하여 실행할 수 있다.

 

그럼 두 함수를 동시에 실행하는 방법을 알아보자.

 

import asyncio
import time

async def print_test(delay, words):
    print(f"before:{words}")
    await asyncio.sleep(delay) 
    print(f"after:{words}")
    
    
async def main():
    print(f"start :{time.strftime('%X')}")
    await print_test(2,"first task start")
    await print_test(1,"second task start")
    
    print(f"Finished :{time.strftime('%X')}")
    
asyncio.run(main())
# start :17:09:03
# before:first task start
# after:first task start
# before:second task start
# after:second task start
# Finished :17:09:06

위 두 코드는 사실 모두 동기 방식과 크게 차이점이 존재하지 않는다. 왜냐면, 순서대로 실행하여 순서대로 출력을 진행하기때문에 병렬적으로 처리했다고 보기 힘들다. 여기서 중요한 부분은 awiat 부분인것 같다. await를 만나면, 말 그대로 끝날때까지 기다리는 작업을 하게 된다. 즉, 해당 테스크가 끝날때까지 기다리게 되므로, 위 코드들은 동기화 순서와 같이 출력되는 것을 볼 수 있다.

 

 

이제 코드를 조금 수정하여 비동기 코드로 수행되는 것을 봐보자.

import asyncio
import time

async def print_test(delay, words):
    print(f"before:{words}")
    await asyncio.sleep(delay) 
    print(f"after:{words}")
    
    
async def main():
    print(f"start :{time.strftime('%X')}")
    task1 = asyncio.create_task(print_test(2,"first task start"))
    task2 = asyncio.create_task(print_test(1,"second task start"))
    await task1
    await task2
    
    print(f"Finished :{time.strftime('%X')}")
    
asyncio.run(main())

start :17:32:14
before:first task start
before:second task start
after:second task start
after:first task start
Finished :17:32:16

위 코드는 create_task를 통해 같은 비동기 함수를 생성한 후 실행하게 된다. 결과를 살펴보면 task1, task2가 시작되고,

task2의 끝나는 시점이 더 빠르니 출력은 task2부터 한것을 볼 수 있다.

즉, task1이 먼저 실행됐지만, 비동기적으로 수행하기 때문에 먼저 종료되는 task2가 실행되는 것이다. 

 

create_task는 새로운 테스크를 만들고, 자동적으로 실행하게 만들어 준다.

await를 통해 task1,task2 중 더 늦게 끝나는 시간까지 기다리게 되고, 그 후 마지막으로 종료 시간을 출력하는 코드다.

이 코드는 동시에 두개의 함수가 각각 실행된 것을 볼 수 있다.

 

여기서, 개인적으로 이해하기 위해 main , task1 , task2의 흐름이 생긴다고 생각하면 편하다.

처음. main의 흐름을 따라 실행되다, await task1, task2를 만나면 처음 생성된 task1로 실행 흐름을 바꾼다. 그 후 await sleep을 만나면 , main으로 실행 흐름을 변경하고, task2로 실행 흐름을 넘겨준다. task2도 await를 만나 main으로 실행 흐름을 돌려주고, task1의 sleep이 끝나 다시 task1으로 실행 흐름을 넘겨주고 모두 끝낸 후 main으로 넘겨준다. 다시 task2로 넘겨줘 종료하고 main으로 실행흐름을 넘겨줘 마지막으로 Finished 시간을 출력하고 종료하게 되는 그런 그림이라 판단된다.

 

import asyncio 
import time

async def greetings(words):
    print(f"{words} hello!")
    await asyncio.sleep(1)
    print(f"{words} bye!")
    
async def main():
    await asyncio.gather(greetings("First"), greetings("Second"))
    
def say_hello():
    start = time.perf_counter()
    asyncio.run(main())
    elapsed = time.perf_counter() - start
    print(f"Total time elapsed:{elapsed}")
    
say_hello()

First hello!
Second hello!
First bye!
Second bye!
Total time elapsed:1.0150065999478102

위와같이 gather를 사용하여 하나씩 await를 진행하지 않고 사용할 수도 있다.

1. say_hello 함수는 비동기 함수가 아니기에 순차적으로 실행된다.

2. 비동기 함수인 main은 비동기적으로 수행되어, greetings 함수 2개가 비동기적으로 수행되게 된다.

3. gather를 이용하면, await를 사용한 효과를 누릴 수 있다.

 

결과를 살펴보면, 두 함수가 실행되고 종료되는 것을 볼 수 있다. ( 동기적으로 실행되지 않았다 ! ) 

 

지금까지 기본적인 비동기 함수를 살펴보았는데, 빠지지 않은 키워드는 await이다.

await 키워드를 만나면, 끝날때까지 해당 함수를 기다리는 역할을 한다고 생각하면 되는데, 파이썬에서의 '코루틴'은 이 역할을 충실하게 수행한다. 그래서, 위 코드의 greetings함수를 단순 실행해보면, return으로 자동으로 코루틴 객체를 반환하는 것을 볼 수 있을 것이다.

대게 일반적인 함수는 함수를 실행 -> 종료순을 거쳐 함수안의 정보를 지속적으로 가지고 있지 않는다.

한가지 대표적인 예제가 함수안 변수이다. 함수안 변수는 함수가 종료되는 순간 없어져 접근할 수 없게 된다.

하지만, 코루틴으로 작성된 함수는 함수를 종료시킨다기 보다, 잠깐 제어권을 다른곳에 넘겨준다고 볼 수 있다.

즉, 현재 말하고 있는 비동기 개념과 아주 유사하다고 할 수 있다.

그래서 파이썬에서 async 비동기 이야기를 할때 빠질 수 없는게 코루틴이다.

 

지금까지 비동기 프로그램 예제를 살펴보았다. 하지만, 비동기 프로그램을 공부하다 보면, 가장 의문이 가는 점이 있다.

동시성, 병렬성 이 두가지가 정말 헷갈린다. 

필자가 판단하기로 지금까지 했던 것은 동시성에 해당된다. 즉, 하나의 코어에서, 여러작업을 동시에 일어나는 것처럼 수행하는 것. 즉, 시간이 날때마다 다른 작업을 하는 멀티테스킹이다. ( 한 사람이 여러 작업을 조금씩 해가는 것 )

하지만, 실질적으로 독립적으로 작업을 수행하는 것이 병렬성이다. ( 여러 사람이 하나의 작업을 하는 것) 이것이 프로세스라 생각한다. 프로세스는 서로다른 코어를 통해 작업을 진행하므로 우리들이 생각하는 진정한 병렬성이라 생각된다.

 

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함