본문 바로가기

Python

[Python] 파이썬 동시성 프로그래밍 (1)

파이썬 동시성 프로그래밍


회사에서 WAS로 Sanic을 사용하고 있다. 나는 지금까지 Spring, Flask, Django 그러니까, 동기적 웹 프레임워크만 사용했다보니, Sanic의 비동기 프로그래밍의 매콤함을 맛보고 그만 정신을 못차려 버렸다... 요새 비동기 프로그래밍이 핫하다고 하던데... 비동기 프로그래밍을 본격적으로 공부하면서 정리한 내용을 조금씩 공유하려고 한다.


바운드


프로그램이 실행될 때 속도가 저하되는 현상


I/O 바운드

  • 프로그램이 실행될 때 I/O에 의해 실행속도가 제한됨 (네트워크 등)

CPU바운드

  • 프로그램이 실행될 때 CPU속도에 의해 실행속도가 제한됨 (복잡한 계산 등)

블로킹

  • 바운드에 의해 코드가 멈추게 되는 현상

동기 & 비동기


동기(sync)

  • 코드가 동기적으로 동작한다 == 코드가 반드시 작성된 순서 그대로 실행된다.

비동기(async)

  • 코드가 비동기적으로 동작한다 == 코드가 반드시 작성된 순서 그대로 실행되는 것은 아니다.

파이썬 코루틴


루틴

  • 일련의 명령, 코드의 흐름

메인루틴

  • 프로그램의 메인 코드의 흐름

서브루틴

  • 우리가 알고있는 보통의 함수나 메소드 def main(): return 'sub routine' (메인 루틴을 보조하는 역할)
  • 하나의 진입점하나의 탈출점이 있는 루틴임
  • 프로그램이 실행되면 서브 루틴은 별도의 공간(스코프)에 해당 로직들을 모아둠
  • 서브루틴이 호출이 될 때, 해당하는 로직들의 공간으로 이동했다가 return을 통해 원래 호출 시점(메인 루틴)으로 돌아오게 된다.
  • return이 없는 함수는 return None이 생략된 것

코루틴

  • 서브루틴의 일반화된 상태, 다양한 진입점다양한 탈출점이 있는 루틴임.
  • 코루틴은 서브루틴과 달리 해당 로직들이 진행되는 중간에 멈추어서 특정 위치로 돌아갔다가 다시 코루틴에서 진행되었던 위치로 돌아와 나머지 로직을 수행할 수 있다.
  • 파이썬 비동기 함수는 코루틴 함수로 만들 수 있다.

asyncio


파이썬은 3.2부터 async/await 지원
공식문서



# 일반적은 서브루틴 (그냥 함수)
def hello_world():
    print("hello world")
    return 123

# 메인루틴
if __name__ == "__main__":
    hello_world()

일반적인 서브루틴은 위와 같이 많이 보던 형태이다.



# 코루틴
# await은 async함수(코루틴) 안에서만 선언 가능하며, awaitable 객체 앞에만 사용가능하다.
async def hello_world(): # 난 이 함수를 비동기로(코루틴으로) 선언하겠어
    print("hello")
    await asyncio.sleep(1) # 이 명령은 다 실행될 때 까지 넘어가지 말고 기다려
    print("world")
    return 123

# 메인루틴
if __name__ == "__main__":
    asyncio.run(hello_world()) # asyncio 패키지를 사용하여 메인루틴의 함수 실행

코루틴 함수로 선언하면 위와 같다.


어웨이터블(awaitable) 객체


어웨이터블(awaitable)객체의 세가지 유형: 코루틴, 태스크 및 퓨처.


코루틴

  • 파이썬 코루틴은 어웨이터블이므로 다른 코루틴에서 기다릴 수 있다.

태스크

  • 태스크는 코루틴을 동시에 예약하는 데 사용됨

퓨처 (공식문서 복붙)

  • 그동안 파이썬은 동시성을 처리하기 위해 threading 과 multiprocessing 패키지를 이용했다.
  • 3.2버전부터, 비동기 실행을 위한 API를 보다 고수준으로 만들고 사용하기 쉽도록 개선한 concurrent.futures 모듈이 도입되어, 멀티스레딩/멀티프로세싱처리의 API가 통일되었다
  • ex_ ProcessPoolExecutor / ThreadPoolExecutor
  • Future는 비동기 연산의 최종 결과를 나타내는 어웨이터블 객체이다.
  • 콜백 기반 코드를 async/await와 함께 사용하려면 asyncio의 Future 객체가 필요함.

프로세스와 스레드


프로그램

  • 어떤 문제를 해결하기 위해 컴퓨터에게 주어지는 처리 방법과 순서를 기술한 일련의 명령문의 집합체
  • 프로그램은 HDD or SDD와 같은 저장장치에 보관되어 있다.

프로세스

  • 사용자는 여러 방법으로 프로그램을 실행시킨다.
  • 프로그램이 실행되면, 해당 프로그램의 작성된 코드들이 주 메모리로 올라와 프로세스가 생성되고 작업이 진행된다.
  • 프로세스가 생성되면 CPU는 프로세스가 해야할 작업을 수행한다.

프로그램: 저장장치에 저장된 정적인 상태
프로세스: 실행을 위해 주메모리에 올라온 동적인 상태

스레드

  • 프로세스가 생성되면 CPU는 프로세스가 해야할 작업을 수행한다. 이때 CPU가 처리하는 작업의 단위가 스레드이다.
  • 다시 말해, 프로세스 내에서 실행되는 여러 작업의 단위를 의미한다.
  • 스레드가 한 개로 동작하면 싱글스레드, 여러개의 스레드가 동작하면 멀티스레드

싱글스레드

  • 단일스레드로 구성, 파이썬의 코루틴이 싱글스레드로 구현된다.

멀티스레드

  • 멀티스레딩에서 다수의 스레드는 메모리의 공유와 통신이 가능함.
    • 장점: 자원의 낭비를 막고 효율성을 향상
    • 단점: 한 스레드에 문제가 생기면 전체 프로세스에 영향을 미침

동시성(병행성)과 병렬성


동시성(Concurrency)

  • 한번에 여러 작업을 동시에 다루는 것을 의미 (by 컨텍스트 스위칭)
  • 동시성은 논리적 개념으로 싱글 스레드에서 사용된다. (물론 멀티스레딩에서 사용될 수 도 있다.)
  • 또한 싱글 코어 뿐만 아니라 멀티 코어에서도 각각의 코어가 동시성을 사용할 수 있다.
  • 싱글스레드에서 사용되는 동시성: asyncio

병렬성(Parallelism)

  • 한번에 여러 작업을 병렬적으로 처리하는 것을 의미 (at the same time)
  • 병렬성은 물리적 개념으로 멀티코어에서 여러 작업을 병렬적으로 수행
  • 동시성과 병렬성은 공존할 수 있다.

멀티스레드는 코어 하나를 여러번 쪼개서 쓰는 것
따라서 cpu바운드 작업에는 비효율적이며 네트워크 바운드에는 유용하다. \

멀티프로세스는 여러 코어를 사용하는 것
따라서 cpu바운드 작업을 할 때 유용하다.

파이썬에서는 GIL때문에 멀티스레드가 병렬적으로 구현 될 수 없다.


파이썬 멀티 스레딩과 멀티 프로세싱


멀티 스레딩의 단점

  • 멀티 스레딩의 장점이자 단점은 스레드끼리 자원을 공유하는 것에 있다.
  • 하나의 자원을 동시에 여러 스레드가 가져가려는 상황이 발생할 수 있는데 이 경우 충돌이 발생할 수 있다.
  • 이 경우 하나의 스레드가 다른 스레드에 의해 차단될 수 있다.
  • 파이썬의 경우 GIL이 이러한 문제를 예방한다.

GIL(Global Interpreter Lock, 전역 인터프린터 잠금)

  • 한번에 한개의 스레드만 유지하도록 잠금
  • GIL은 본질적으로 한 스레드가 다른 스레드를 차단해서 제어를 얻는것을 막아준다. (멀티 스레딩의 위험으로부터 보호하는 것)
  • 따라서 파이썬은 스레드로 병렬성 연산을 할 수 없다.
  • 동시성을 이용한 멀티스레딩을 사용하여 I/O바운드에서 유용하게 사용할 수 있지만 CPU 바운드에서는 효율이 떨어진다.

따라서 CPU 바운드 작업에서는 멀티 프로세싱을 이용한다.

'Python' 카테고리의 다른 글

[Python] 얕은 복사, 깊은 복사  (0) 2021.09.29
[Python] Module 정리  (3) 2021.09.13
[Python] 모국어같은 파이썬... & Class 정리  (1) 2021.09.13