동기 방식은 작업 A가 작업 B에게 작업 요청을 하고 작업 A가 작업 B가 작업을 끝낼 때까지 관심을 가지는 방식이다. 여기서 말하는 관심은 작업이 끝이 났는가에 대한 확인이다.
비동기 방식은 작업 A가 작업 B에게 작업 요청을 하고 작업 A가 작업 B가 작업을 끝낼 때까지 관심을 버린다. 즉, B가 작업이 끝났다고 A에게 알려줄 때까지 작업이 끝났는가에 대해 확인하지 않는다.
예를 들어 웹 게시판 서버 2개를 각각 Sync와 Async로 운영한다고 가정하고 게시글을 올리고 사용자가 올라간 게시물을 확인하는 과정을 살펴보면 다음과 같다.
위 그림을 살펴보면 Client가 게시글을 올리고 난 후, 지속적으로 서버에게 게시글이 올라갔는 지 확인한다. 이처럼 클라이언트가 작업에 종료 여부를 계속 확인하는 것이 Sync 방식이다.
Block은 작업 A가 작업 B에게 작업 요청을 하고 작업 A는 작업 B가 작업을 끝낼 때까지 아무 동작을 하지 않는 것이다.
Non-block은 Block과 반대로 작업 A가 작업 B에게 작업 요청을 하고 작업 A는 작업 B가 끝나지 않더라도 다른 작업(ex 작업 C)을 우선적으로 하는 것이다.
이 두 개념은 그닥 어려운 개념이 아니자만 위에 언급됐던 Sync & Async와 함께 사용되기 때문에 꽤나 헷갈린다.
위에 예시로 들었던 웹 게시판 서버에 Block과 Noe-block이 사용되는 경우에 대해 살펴보자.
위 Sync 서버를 살펴보면 지속적으로 게시글이 올라갔는지 확인해야 하기 때문에 네트워크 리소스 소모율이 크다. 따라서 처음 게시글 업로드 요청을 보내고 나서 업로드가 완료될 때 까지 Block 시켜 결과물을 받아온다면 네트워크 리소스 소모율을 줄일 수 있다
다음으로 Async 서버를 살펴보면 게시글을 올린 다음 Client는 Server가 업로드 끝났다고 알려줄 때까지 CPU(또는 I/O) 자원이 낭비된다. 따라서 Server로부터 완료됐다고 알림이 오기 전까지 Non-block을 통해 다른 작업을 처리해서 CPU 자원 낭비를 줄일 수 있다.
Async & Block 조합과 Sync & Non-block 조합은 위에서 살펴봤듯이 각각 네트워크 리소스와 CPU(혹은 I/O)리소스를 낭비한다.
따라서 실제로 많이 쓰이진 않기 때문에 별도로 다뤄보진 않을 것이다.
import time
def a():
print("start in a()")
time.sleep(2)
print("finished in a()")
def b():
print("start in b()")
time.sleep(2)
print("finished in b()")
def task():
a()
b()
def main():
task()
main()
import asyncio
async def a():
print("start in a()")
await asyncio.sleep(2)
print("finished in a()")
async def b():
print("start in b()")
await asyncio.sleep(2)
print("finished in b()")
async def task():
asyncio.create_task(a())
asyncio.create_task(b())
await asyncio.sleep(3)
async def main():
await task()
asyncio.run(main())
기본적으로 Async & Non-block은 thread나 process와 동작이 비슷하다. 최근 공부하고 있는 flutter의 dart나 nest js의 node js는 단일 프로세스로 동작해서 병렬처리가 어려울 것 같은데 잘되는 이유는 이 Async & Non-block을 잘 활용해서인 듯 하다. 실제로 두 언어 모두 기본적으로 비동기 방식을 선호하기 때문이다.