데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      Python 개발자를 위한 비동기 개념정리

      바이브코딩 25.07.09
      2,923 7 1
      DEVOTEE 요약
      비동기는 작업 완료를 기다리지 않고 다른 작업을 수행할 수 있는 방식으로, 동기와는 호출자의 대기 여부에서 차이가 있습니다. 비동기는 I/O 대기 시간이 많거나 동시 작업이 필요한 경우에 적합하지만, CPU 중심 작업에서는 성능 저하와 코드 복잡성을 초래할 수 있습니다. 따라서 비동기를 필요에 따라 효과적으로 활용하는 것이 중요하며, 후속 포스팅에서 더 자세한 내용을 다룰 예정입니다.

      들어가며

      안녕하세요. SK AX의 바이브코딩입니다. 요즘 Python으로 개발을 하면서 비동기 로직을 접하는 일이 많아졌습니다.

      특히 FastAPI의 등장 이후, LLM을 연동하는 작업이 늘면서 비동기의 중요성과 활용 빈도가 크게 높아졌습니다.

      체감적으로는 비동기를 이해하고 있지만, 막상 정확히 설명하려면 혼란스러운 부분이 있었습니다. 이번 포스트에서는 비동기 관련 핵심 용어들을 정리해보려 합니다.


      동기/비동기란?

      비동기(Asynchronous)란 용어를 살펴보려면 먼저 그 반대되는 개념인 동기(synchronous)를 알아야 합니다.

      두 단어 모두 일상 생활에서는 잘 사용하지 않았던 용어이고, 프로그래밍 쪽에서 활발하게 사용하는 용어일텐데요. 단어 개념부터 짚고 가겠습니다.


      사전적인 의미부터 살펴보겠습니다.

      • Synchronous (동기)

        • 접두사 syn-은 같은(together), -chronous는 시간(time)이라는 뜻을 가집니다.

        • 즉 같은 시간에 함께 진행되는 것, 동시에 발생하는 것 이라는 의미가 됩니다.

      • Asynchronous (비동기)

        • 접두사 a-는 부정을 나타내는 접두어로, 같지 않은(not)을 의미합니다.

        • 따라서 동시에 일어나지 않는, 시간적으로 분리된 상태 를 의미합니다.

      프로그래밍에서 사용하는 의미를 알아보겠습니다. 프로그래밍에서 동기/비동기는 호출자(caller) 관점에서 작업의 처리 순서와 대기 시간 처리 방식에 대해 이야기합니다.

      • 동기

        • 요청한 작업이 끝날 때까지 다음 작업을 하지 않고 기다리는 방식입니다.

        • 호출자(caller)가 함수를 호출할 경우, 함수의 리턴값(혹은 예외)을 받아야 다음 로직 수행이 가능합니다.

      • 비동기

        • 작업이 완료되기를 기다리지 않고, 다른 작업을 먼저 수행하는 방식입니다.

        • 호출자(caller)가 함수를 호출할 경우, 결과를 기다리지 않고 다음 로직을 수행합니다.

      간단한 예제 코드를 보겠습니다. 파일을 다운로드 하는 코드입니다.

      먼저 동기 방식입니다.

      def download_file():
          print("파일 다운로드 시작")
          time.sleep(3)  # 다운로드 로직 대신 편의상 sleep으로 대체
          print("파일 다운로드 완료")
      
      print("프로그램 시작")
      download_file()
      print("다음 작업")

      실행하면 아래처럼 출력됩니다.

      프로그램 시작
      파일 다운로드 시작
      파일 다운로드 완료
      다음 작업

      "프로그램 시작" 후 download_file() 함수를 호출했는데, 이 함수가 3초 정도 소요됨에도 함수가 끝날때까지 기다린 후 "다음 작업"을 수행한 것을 볼 수 있습니다.


      다음은 비동기 방식입니다.

      import asyncio
      
      async def download_file():
          print("파일 다운로드 시작")
          await asyncio.sleep(3)  # 다운로드 로직 대신 편의상 sleep으로 대체
          print("파일 다운로드 완료")
      
      async def main():
          print("프로그램 시작")
          task = asyncio.create_task(download_file())
          print("다음 작업")
          await task
      
      asyncio.run(main())

      실행하면 아래처럼 출력됩니다.

      프로그램 시작
      파일 다운로드 시작
      다음 작업
      파일 다운로드 완료

      "프로그램 시작" 후 download_file() 함수를 호출했는데, 이 함수가 끝날때까지 기다리지 않고 "다음 작업"을 수행한 것을 볼 수 있습니다.


      블로킹/논블로킹 이란?

      동기/비동기의 개념을 살펴보다 보면 같이 나오는 용어가 있습니다. 블로킹과 논블로킹입니다.

      동기/비동기와 비슷한 의미인 거 같으면서도 바라보는 관점이 조금 다릅니다.

      역시 단어 개념부터 짚고 가는 게 좋아 보입니다. 블로킹과 논블로킹은 동기/비동기보다는 조금 직관적으로 파악이 가능합니다.

      • Blocking

        • 막다, 차단하다의 의미를 가지고 있습니다.

        • **어떤 작업이 수행되고 있으면, 다른 일은 할 수 없게 막는 상태를 나타냅니다. **

      • Non-blocking

        • Blocking의 반대 의미입니다.

        • 작업 중 대기하지 않고 다른 일을 계속할 수 있는 상태를 나타냅니다.

      수행하고 있는 작업이 자원을 점유하고 있으면, 그 자원을 쓰는 다른 작업은 기다릴 수밖에 없겠죠. 여기에서의 자원은 보통 CPU인 경우가 많습니다.

      수행되고 있는 작업이 I/O 대기하고 있을 때도, CPU를 점유하고 놓지 않는 것이죠.

      그러면 CPU를 사용해야 하는 다른 작업은 수행할 수 없습니다. 이것이 블로킹의 개념이며, 논블로킹은 이를 반대로 이해하면 됩니다.


      코드를 한 번 볼까요? 파일을 읽는 코드입니다.

      먼저 블로킹입니다.

      def read_file():
          with open("data.txt") as f:
              data = f.read()  # 이 작업이 완료될 때까지 기다림
          return data

      위 코드의 read_file() 함수에서는 함수가 작업을 마칠 때까지 프로그램이 흐름을 멈춥니다.


      다음은 논블로킹입니다.

      import asyncio
      import aiofiles
      
      async def read_file():
          async with aiofiles.open("data.txt", mode='r') as f:
              data = await f.read()  # 실제 읽는 작업은 논블로킹
          return data

      코드 형태는 비슷하지만, 함수는 즉시 반환되고 결과는 나중에 준비됩니다.


      헷갈리는 개념에 대해 다시 정리!

      동기/비동기, 블로킹/논블로킹의 개념을 정리해보았습니다.

      그런데 뭔가 비슷해보이고 헷갈립니다. 둘 다 기다리는 개념인 거 같기도 하고요.


      그래서 두 개념의 차이를 간단히 정리해보겠습니다.

      동기/비동기는 호출자(caller) 관점에서 바라보는 로직 흐름 관점의 설계 방식 이고,

      블로킹/논블로킹은 함수나 시스템 호출 자체의 동작 방식에 초점을 둔 시스템 레벨 개념 입니다.


      예제로도 한 번 설명해 보겠습니다.

      동기/비동기의 경우 친구들과 연락하는 것을 생각해 볼 수 있습니다.

      전화로 연락을 한다면 앞 친구와 통화를 마쳐야 다음 친구에게 전화를 걸 수 있겠죠. 이것이 동기입니다.

      문자로 연락을 한다면 여러 명에게 문자를 보내 놓고, 응답이 오는 친구에게 또 문자를 이어나갈 수 있겠죠. 이것이 비동기입니다.


      블로킹/논블로킹은 헬스장에서 기구를 사용하는 것을 예로 들어보겠습니다. 하나의 기구를 여러 세트 수행한다고 할 때, 한 세트 후 쉴 때 어떻게 하는지에 따라 블로킹과 논블로킹을 나눌 수 있습니다.

      한세트 후 쉴 때도 기구에 앉아서 쉬면서 다른 사람이 기구를 사용하지 못하게 하는 것이 블로킹, 쉴 때는 잠시 일어서서 다른 사람이 사용가능하게 하는 것이 논블로킹입니다.


      동기/비동기/블로킹/논블로킹 4분면

      일반적으로 동기는 블로킹, 비동기는 논블로킹인 경우가 대부분입니다.

      하지만 동기와 블로킹이 같은 용어는 아니다보니 꼭 그렇지 않은 경우도 생깁니다. 드물지만 동기+논블로킹인 경우도 있고, 비동기+블로킹인 경우도 있습니다.

      이 부분을 담기에는 내용이 복잡해서.. 아래 URL을 참고하세요.

      image.png

      https://developer.ibm.com/articles/l-async/


      근데 그럼 그냥 다 비동기로 하면 되는 거 아닌가요?

      다른 이야기가 길었죠? 다시 비동기로 돌아와보겠습니다.

      개념을 정리해보니 비동기가 참 좋아보입니다.

      작업을 요청 후 기다리지 않고 다른 작업을 수행할 수 있다니, 효율성이나 성능 면에서 장점이 있어 보입니다.

      그렇다면 이런 의문이 들 수 있습니다. "모든 작업을 비동기로 처리하면 되는 것 아닐까?"

      꼭 그렇진 않습니다. 오히려 비동기는 필요할 때만 쓰는 게 정답입니다.

      비동기는 I/O 작업이 많은 곳에서는 놀라운 성능 향상을 가져올 수 있지만, 무턱대고 적용하면 코드가 복잡해지고 오히려 성능이 떨어지는 경우도 있습니다.


      그럼 비동기는 언제 쓰는 게 좋나요?

      비동기는 I/O 작업이 많은 경우에 쓰는 것이 좋습니다.

      • REST API 호출이나 웹 크롤러 등 네트워크 요청 처리가 많은 경우

      • 대용량 파일 처리나 비동기 로그 저장 등 파일 입출력을 다루는 경우

      • 비동기 DB 드라이버를 사용해 DB를 연결하는 경우

      • 채팅 서버 등에서 수많은 동시 다중 작업을 처리하는 경우

      이런 상황에서는 CPU는 놀고, I/O를 기다려야 하므로, 비동기를 활용하면 대기 시간 동안 다른 작업을 처리할 수 있습니다. 당연히 효율성이 극대화되겠죠.


      비동기를 쓰기에 적합하지 않은 경우

      비동기는 CPU-bounded 작업에는 적합하지 않습니다.

      • 대규모 행렬 연산 등 복잡한 수치를 계산

      • 이미지나 영상을 처리하는 작업

      • 압축, 암호화 등 CPU를 지속적으로 사용하는 작업

      이런 작업은 CPU를 많이 사용해서 비동기를 써도 성능이 향상되지 않습니다.

      오히려 이벤트 루프를 점유해서 전체적인 성능을 저하시킬 수도 있습니다.

      (이번 포스트에서는 다루지 않지만) CPU를 많이 쓰는 작업에서는 멀티프로세싱이 더 적합합니다.


      비동기의 장단점 정리

      비동기를 쓰기에 적합한 상황과 그렇지 않은 상황을 알아보았습니다. 비동기의 장단점을 다시 정리해보겠습니다.

      장점

      • 높은 동시성 처리 능력 : 수많은 작업을 효율적으로 처리 가능합니다.

      • 낮은 리소스 소비 : 쓰레드나 프로세스를 생성하지 않기 때문에 메모리와 CPU의 부담이 적습니다.

      • 빠른 응답성 : 대기 시간 없이 다음 작업 수행이 가능합니다.

      단점

      • 코드의 복잡도 증가 : 코드의 흐름이 직관적이지 않고, 그에 따라 디버깅 난이도도 높아집니다.

      • CPU-bounded 작업에서 비효율 : 오히려 속도 저하를 유발할 수도 있습니다.

      • 비동기를 지원하지 않는 라이브러리가 많음 : 기존에 동기 라이브러리와 호환이 어렵습니다.


      마치며

      비동기 관련하여 개념을 정리해보았습니다.

      먼저 동기/비동기의 개념과 블로킹/논블로킹의 개념을 정리하고 비교해보았고, 어떤 상황에서 비동기를 쓰는 게 유리한지도 알아보았습니다.

      후속 포스팅에서는 python에서 비동기 처리를 작성하는 방법을 다뤄보도록 하겠습니다.

      이후 멀티쓰레딩/프로세싱 부분 등 관련 개념과 작성 방법도 정리해보도록 하겠습니다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      바이브코딩 님의 최신 블로그

      더보기
      동영상 기고하기