파이썬 동시성 프로그래밍
회사에서 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 |