티스토리 뷰
파이썬 공부를 하다보면 어느순간부터 기본적인 내용이 아닌, 보다 심층적인 내용을 공부해야겠다고 느낄때가 있다.
특히, 프로그래밍을 하다보면, 속도가 가장 큰 이슈로 발생하는데, 필자는 정형데이터를 전처리하는 과정에서 속도측면을 개선해보고자 '비동기 방식'을 접하게 되었다.
일단, 상식적으로 병렬적으로 처리한다는 내용은 누구나 다 알것이지만, 막상 코드로 작성하려고 하니, 어렵다.
그래서 이 포스팅을 통해 정리하고자 한다.
누구나 쉽게 접할 수 있도록, 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 비동기 이야기를 할때 빠질 수 없는게 코루틴이다.
지금까지 비동기 프로그램 예제를 살펴보았다. 하지만, 비동기 프로그램을 공부하다 보면, 가장 의문이 가는 점이 있다.
동시성, 병렬성 이 두가지가 정말 헷갈린다.
필자가 판단하기로 지금까지 했던 것은 동시성에 해당된다. 즉, 하나의 코어에서, 여러작업을 동시에 일어나는 것처럼 수행하는 것. 즉, 시간이 날때마다 다른 작업을 하는 멀티테스킹이다. ( 한 사람이 여러 작업을 조금씩 해가는 것 )
하지만, 실질적으로 독립적으로 작업을 수행하는 것이 병렬성이다. ( 여러 사람이 하나의 작업을 하는 것) 이것이 프로세스라 생각한다. 프로세스는 서로다른 코어를 통해 작업을 진행하므로 우리들이 생각하는 진정한 병렬성이라 생각된다.
'적어놓으면 쓸모있는 코드' 카테고리의 다른 글
[Windows] C 언어 설정 - vscode (4) | 2024.07.02 |
---|---|
[Selenium] 0. Selenium 설치 (0) | 2023.11.29 |
[cv2] video 데이터 증량하기 (0) | 2023.02.22 |
github token 자동 로그인 (0) | 2022.09.09 |
파이캐럿에서 피쳐수가 자동으로 줄어들때 (0) | 2022.08.10 |
- Total
- Today
- Yesterday
- V11
- GIT
- docker
- yolov11
- 이미지
- 도커
- 깃
- c3k2
- YOLOv8
- CNN
- 자바
- 티스토리챌린지
- 디텍션
- 오류
- 초보자
- 오블완
- 정리
- 어탠션
- 백준
- GNN
- 알고리즘
- github
- 딥러닝
- Tree
- python
- 뜯어보기
- 욜로
- java
- DeepLearning
- YOLO
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |