다음의 내용은 "real python" 사이트의 "Speed Up Your Python Program With Concurrency" 아티클을 번역/정리한 내용입니다.
이 아티클에서는 아래의 것들을 알게된다:
동시성의 사전적 정의: 동시에 발생함
파이썬에서, "동시에 발생하는 것"을 부르는 다양한 이름들(thread, task, process)이 있다.
하지만 고차원에서 보면,
"thread, task, process" 등의 동시에 발생하는 것들은 "순서대로 동작하는 명령어들의 순서를 의미"한다.
"동시에 발생하는 것들"은 서로 다른 생각의 기차들과 같다.
각각은 특정 포인트에서 멈추고, (멈추면 cpu 또는 두뇌가 그것들을 프로세싱함)
각각은 다른 것들로 스위치(전환)할 수 있고,
각각의 상태는 저장되기 때문에, interrupt되었던 시점에 저장되었던 상태에서 다시 시작할 수 있다.
"동시적(simultaneous)"이라는 의미를 살펴보자.
자세히 살펴보면, 오직 "멀티프로세싱"만이 진짜로 실질적으로 말그대로 동.시.에. 실행된다.
"threading"과 "asyncio"는 둘다 "싱글" 프로세서라서 한번에 하나가 실행된다.
threading과 asyncio는 전반적은 프로세스의 속도를 높이기 위해서 번갈아 실행하는 영리한 방법을 찾았다.
비록 threading과 asyncio가 동시에 실행되는 것은 아니지만, 여전히 동시성(concurrency)라고 부른다.
thread 또는 task가 번걸아가면서 실행되는 방법은
threading일 때와 asyncio일 때와 큰 차이가 있다.
threading일 때는,
thread가 언제든 운영체제를 interrupt해서 다른 thread를 실행하도록 할 수 있음을 운영체제는 알고 있다.
이를 pre-emptive multitasking(선점형 멀티태스킹)이라고 한다.
왜냐하면 os가 switch(문맥전환)를 하기위해서 당신의 thread를 선점할 수 있기 때문이다.
pre-emptive multitasking(선점형 멀티태스킹)은 switch(문맥전환)을 할 필요가 없다는 점에서 thread 코드 상에서 간단하다.
pre-emptive multitasking(선점형 멀티태스킹)는 어렵다. 왜냐하면 "at any time(언제든지)"라는 부분을 만족해야해서.
switch(문맥전환)은 파이썬 코드 단 1줄을 실행하는 와중에도 발생할 수 있다.
"Asyncio" 는 반면,
협동적 다중 작업(cooperative multitasking)을 사용한다.
task들을 협력해야한다.
언제 switch(문맥전환)할 준비가 되었는지 알려줌으로써 서로 협력해야한다.
이는 task의 코드가 이를 가능하도록 약간의 수정이 필요함을 의미한다.
위의 내용을 앎으로써의 장점은
언제 당신의 task가 swapped out 될지 항상 알 수있게 된다는 점이다.
이것은 명령문이 표시(marked)가 되지 않는한, 파이썬 명령문의 중간에서 스와아웃되지는 않는다.
위에서 언급된 동시성은 싱글 프로세서일때!
만약, 프로세서가 여러개라면?!
멀티프로세싱이 답이다.
멀티프로세싱을 가지고, 파이썬은 새로운 '프로세스'들을 생성한다.
여기서 말하는 '프로세스들'이란 서로다른 프로그램을 의미한다.
프로세스는 보통 "메모리, 파일과 같은 자원들의 모음"으로 정의된다.
각각의 프로세스는 각각의 고유한 파이썬 인터프리터에서 실행된다.
왜냐하면, 다양한 프로세스들이 있기 때문이다.
각각의 멀티프로세싱 프로그램의 각각의 당산의 생각열차들은 서로다른 코어(cpu)에서 실행할 수 있다.
다른 코어에서 실행된다는 말은, 각각이 서로 다른 코어에서 동시에 실행될 수 있다는 말이다.
이것을 함에는 복잡함이 있지만 파이썬은 복잡함을 줄이려고 노력하고 있다.
동시성은 크게 2가지 유형의 문제를 만든다.
1) Cpu-bound
2) i/o-bound
I/O-bound 문제는 당신의 프로그램을 느리게 만드는 원인이 될 수 있다.
왜냐하면, 외부 자원(파일시스템, 네트워크 커넥션)으로부터 input/output(I/O)를 자주 기다려야하기 때문이다.
CPU-bound 프로그램은, 프로그램의 속도를 좌지우지하는 것이 CPU이다.
Concurrency를 적용할 때는 적용할 프로그램이 I/O-bound인지, CPU-bound인지 따져볼 필요가 있다.
왜냐하면, bound 타입에 따라서 적합한 concurrency 종류가 있다.
concurrency를 적용하는 것이 프로그램에 추가적인 코드와 복잡성을 주기 때문에,
concurrency를 적용하는 노력과 잠정적인 속도향상의 가치를 따져볼 필요가 있따.
I/O-bound 프로그램과 일반적인 문제: "네트워크를 통해서 콘텐츠 다운받기"
아래의 예제에서는, 몇몇의 사이트에서 웹페이지를 다운받는 예제를 만든다.
이해하기 쉬움
다음의 다른 방법들에 비해 상대적으로 느림
데이터 접근을 thread-safe하도록 만드는 전략들:
1) thread-safe한 자료구조를 갖는 queue 모듈을 사용한다
Queue는 thread-safe한 자료구조를 가지고 있음
2) "threading.Lock"으로 한번에 하나의 쓰레드만이 메모리에 접근하도록 하는 원시적인 방법이 있음
3) local storage 사용하기
"Threading.local()"은 글로벌로 보이지만, 각각의 쓰레드에 국한?된다
빠르다!
Race Conditions
How does asyncio actually work?
https://stackoverflow.com/questions/49005651/how-does-asyncio-actually-work/51116910#51116910
빠르다!
asyncio를 충족하게 쓰려면 특별한 asyncio 라이브러리를 써야함
Global Interpreter Lock (GIL)
https://realpython.com/python-gil/
설정이 슆고 추가 코드가 작음
각 프로세스에서 어떤 부분을 담당하는지 생각해보아야 함
I/O-bound는 대부분의 시간을 외부 동작을 기다리는데 사용함
io-bound와 다르게
cpu-bound에는 기다림이 없다
설정이 쉽고 추가코드가 작음
cpu 파워를 full로 사용가능
프로세서 간의 커뮤니케이션이 많이 요구됨
개별적인 프로세서의 문제를 판단하기 어려움
1) 동시성 모듈을 사용해야 하는가?
- 성능상의 문제가 발견되기 전까지는 결정을 미루기
- 어떤 동시성 타입이 판단되기 전까지는 결정을 미루기
2) 프로그램이 cpu-bound인지 io-bound인지 알아보기
- cpu-bound는 오직 멀티프로세싱이 좋음
- io-bound는 asyncio