동기(synchronous)는 어떤 작업을 진행할 때 순차적으로 진행하는 방식이다. 여러 작업을 수행해야 할 때 순차적으로 진행하는 방식이다. 한 작업이 끝난 후 다음작업을 진행하게된다.
비동기(asynchronous)는 어떤 작업을 진행할때 그 작업이 완료되지 않더라도 다음 코드를 실행하는 방식이다. 한 작업이 진행중임에도 다른 작업을 진행하게 된다. 즉, 병렬처리가 가능하다.
비동기 프로그래밍은 일반적으로 이벤트 기반(event-driven)이나 콜백(callback) 기반으로 구현된다.
비동기 프로그래밍은 주로 I/O 작업이 많은 경우나 여러 작업을 동시에 처리해야 하는 경우에 특히 유용합니다. 네트워크 통신, 웹 개발, 데이터베이스 액세스, 파일 입출력 등의 작업은 대부분 I/O 작업이므로 비동기 방식으로 처리하는 것이 효과적입니다.
응답성(Responsiveness): 병렬처리를 통해 여러 작업을 동시에 수행함으로, 사용자가 작업요청 후 응답을 기다리는 불필요한 시간을 줄여준다.
확장성(Scalability): 여러 작업을 동시에 실행하므로 웹 서버, 네트워크 서비스, 데이터 파이프라인 등과 같은 시스템의 확장성을 향상시키는 데 도움이 된다
자원 효율성(Resource Efficiency): 놀고있는 CPU나 메모리의 공간을 활용할 수 있다.
병렬성(Concurrency): 여러 작업이 동시에 실행될 수 있으며, 이는 멀티코어 프로세서를 활용하여 작업을 분산 처리할 수 있다.
다음은 비동기를 사용하기 전 sum()함수를 사용하여 결과값을 출력하는 함수이다.
import time
def sleep():
time.sleep(1)
def sum(name, numbers):
start = time.time()
total = 0
for number in numbers:
sleep()
total += number
print(f'작업중={name}, number={number}, total={total}')
end = time.time()
print(f'작업명={name}, 걸린시간={end-start}')
return total
def main():
start = time.time()
result1 = sum("A", [1, 2])
result2 = sum("B", [1, 2, 3])
end = time.time()
print(f'총합={result1+result2}, 총시간={end-start}')
if __name__ == "__main__":
main()
sum()함수에 의해 numbers의 요소를 더할 때 마다 1초씩 걸리며, 결과는 다음과 같다.
작업중=A, number=1, total=1
작업중=A, number=2, total=3
작업명=A, 걸린시간=2.000162124633789
작업중=B, number=1, total=1
작업중=B, number=2, total=3
작업중=B, number=3, total=6
작업명=B, 걸린시간=3.0002427101135254
총합=9, 총시간=5.0004048347473145
다음은 위 코드에 async를 적용한 것이다.
import asyncio
import time
async def sleep():
await asyncio.sleep(1)
async def sum(name, numbers):
start = time.time()
total = 0
for number in numbers:
await sleep()
total += number
print(f'작업중={name}, number={number}, total={total}')
end = time.time()
print(f'작업명={name}, 걸린시간={end-start}')
return total
async def main():
start = time.time()
task1 = asyncio.create_task(sum("A", [1, 2]))
task2 = asyncio.create_task(sum("B", [1, 2, 3]))
await task1
await task2
result1 = task1.result()
result2 = task2.result()
end = time.time()
print(f'총합={result1+result2}, 총시간={end-start}')
if __name__ == "__main__":
asyncio.run(main())
async를 적용한 비동기 함수를 코루틴이라 하며, async는 def앞에 await은 호출한 함수 앞에 붙여 사용한다.
코루틴 진행중 await을 만나면 await로 호출한 코루틴이 종료될 때까지 기다리지 않고 제어권을 메인 스레드나 다른 코루틴으로 넘긴다.
이때 sleep함수에서 time.sleep(1)이 아닌 async.sleep(1)을 사용해서 비동기 출력이 가능하다.
asyncio.create_task()는 수행할 코루틴 작업(태스크)을 생성한다. 여기서는 작업을 생성할 뿐이지 실제로 코루틴이 수행되는 것은 아니다. 실제 코루틴 실행은 await 태스크가 담당한다.
작업중=A, number=1, total=1
작업중=B, number=1, total=1
작업중=A, number=2, total=3
작업명=A, 걸린시간=2.000617742538452
작업중=B, number=2, total=3
작업중=B, number=3, total=6
작업명=B, 걸린시간=3.000927209854126
총합=9, 총시간=3.000927209854126
제어권이 await에 의해 계속 바뀌게 되어 A작업과 B작업을 번갈아가며 수행한다.
B작업의 수행시간이 5.x에서 3.x로 줄어든 것을 확인할 수 있다.