이번에는 책의 7장 내용인 '동시성(concurrency)과 병렬성(parallelism)'에 대해 먼저 정리하겠다. 주로 개념들을 잡고 가는 기록이 될 듯하다.
- 동시성 , 병렬성
- subprocess
- thread
- coroutine
개념잡기 - 동시성 & 병렬성
동시성
동시성은 실제로 동시에 작업되는 것은 아니지만, 프로그램을 아주 빠르게 번갈아가며 실행되게 하는 것이다. 그렇기 때문에 작업속도가 빨라지는 일은 거의 없지만, 지연 시간이 있는 실행 경로가 많은 프로그램들을 동시성으로 작업하면 속도가 빨라질 수 있다.
병렬성
병렬성은 같은 시간에 다른 작업을 실제로 같은 시간에 처리하는 것이다. 실제로 병렬적으로 실행되기 때문에 속도가 빨라진다. CPU 코어가 여러 개일 때를 예시로 들 수 있다.
cf.) 참고. 프로세스는 CPU에서 작업을 돌리는데, CPU 1개에서 여러 개의 작업을 돌리는 것은 보통 동시성의 관점에서 볼 수 있다(멀티프로세싱). 반면 CPU가 여러 개라면 실제로도 여러 개의 작업을 진짜 동시에 병렬로 돌릴 수 있다는 것!(==병렬성)
cf.) 프로세스와 스레드는 같지 않다. 스레드는 한 프로세스 안에 있는 친구다.
subprocess
파이썬에서는 자식 프로세스들을 관리할 수 있는데, subprocess 모듈 사용을 권장하고 있다. 이 때 파이썬의 자식 프로세스는 병렬 실행된다. 즉, 속도가 빨라지고 컴퓨터가 가진 모든 CPU를 사용할 수 있다. 아래 첫번째 코드와 같이 run 함수를 사용할 수 있다.
start_time = time.time()
for i in range(50):
result = subprocess.run(['echo', 'hi!'])
result.check_returncode()
print(f'소요된 시간 : {time.time()-start_time}') # 소요된 시간 : 0.19005894660949707
이보다 권장되는 건 Popen을 사용해서 작업하는 것이다. Popen으로 하위프로세스를 만들면 해당 프로세스를 검사(==polling)할 수 있기 때문이다. Popen은 아래와 같이 실행할 수 있는데, 두 번째 코드 덩어리는 .poll()을 활용한 상태 검사코드를 추가한 것이다.
## Popen
start_time = time.time()
subp = []
for i in range(50):
subp.append(subprocess.Popen(['echo', 'hi!']))
for sub in subp:
sub.communicate()
print(f'소요된 시간 : {time.time()-start_time}') #소요된 시간 : 0.17681527137756348
## Popen + poll
## 왜인진 모르겠지만 시간이 더 줄었다
start_time = time.time()
subp = []
for i in range(50):
subp.append(subprocess.Popen(['echo', 'hi!']))
for sub in subp:
while sub.poll() is None: # 작업이 끝나면 poll()의 return값은 0이다.
sub.communicate()
print(f'소요된 시간 : {time.time()-start_time}')# 소요된 시간 : 0.16349339485168457
thread
파이썬의 스레드를 알기 전 사전 지식을 탐험해보자. 파이썬은 보통 Cpython으로 구현체가 되어있는데, 이 Cpython의 프로그램 실행 순서는 다음과 같다.
소스구문 해석 -> bytecode 변환 -> (스택 기반) 인터프리터로 실행
두 번째 순서인 바이트코드 변환을 위해서는 프로그램 실행되는 동안 인터프리터가 일관성을 유지해야 한다. 일관성을 유지하기 위해서 락을 걸어주는데, 이게 바로 GIL(Global Interpreter Lock)이다. 멀티스레드로 혹시라도 인터럽트가 발생하면 인터프리터의 상태가 바뀔 수 있기 때문에, 여기에 제한을 걸어주는 것이다.
여기서 문제가 조금 생긴다. 스레드는 프로그램을 보다 빠르게 실행하고 싶은 목적으로 사용하는 경우가 있는데, GIL을 걸어버리면 속도를 높이는 병렬 처리는 조금 힘들어질 거다. 아래 코드는 파이썬에서 스레드를 사용하는 방법이다.
#thread
from threading import Thread
class MyThread(Thread):
def __init__(self):
super().__init__()
def run(self):
print('1')
start_time = time.time()
threads = []
for i in range(50):
th = MyThread()
th.start()
threads.append(th)
for th in threads:
th.join()
print(f'소요된 시간 : {time.time()-start_time}')#소요된 시간 : 0.03010416030883789
# without thread
start_time = time.time()
for i in range(50):
print(1)
print(f'소요된 시간 : {time.time()-start_time}')#소요된 시간 : 0.0019478797912597656
코드를 보면 알겠지만, 이번 예제에서는 thread를 사용했을 때 소요된 시간이 그렇지 않을 때보다 오래 걸렸다! 스레드 할당, 실행 조정 등에 대해 부가 비용이 드는 것은 차치하더라도, GIL로 인한 특성을 잘 보여주는 것이다.
이런 스레드는 블로킹 I/O에서 유용하게 쓰일 수 있다. 블로킹 I/O는 파이썬 프로그램이 시스템 콜을 사용해 파일 쓰고 읽기, 네트워크 상호작용하기, 통신하기 등등의 작업을 말한다. 이런 작업에서 직렬 실행을 하게 되면 시스템 콜 동안 다른 작업을 아예 할 수 없을 것이다. 이럴 때 thread를 이용하면 프로그램은 병렬로 실행되지 않는 대신, 시스템 콜은 병렬 실행이 가능하다!
cf) python3.6부터는 byte(==8bit)가 아닌 16비트 명령어기 때문에 굳이 따지면 bytecode가 아닌 wordcode라고 한다. 그치만... 'word'code라고 하기엔 한국어는 3byte인걸...!
코루틴
I/O 동시성을 처리하기 위해 사용할 수 있는데, 파이썬 프로그램 내에서 동시에 실행되는 것처럼 보이게 하는 방법이다.
- async : 함수 앞에 await를 쓰면 코루틴 함수로 만들어준다.(generator의 yield 느낌)
- await : 코루틴을 일시 중단시키는데, 해결 후에 async 함수에서 실행이 재개된다.
아래 코드는 코루틴 함수를 정의하는 방법이다. 두 번째 함수인 my_function2를 보면 for 문 안에서 코루틴 함수를 호출하고 있다. 해당 코루틴 함수인 my_function1은 즉시 호출되는 대신 await에서 사용할 수 있도록 인스턴스를 반환해준다.
두번째 함수의 asyncio.gather는 my_function1 코루틴을 동시에 실행하면서 해당 코루틴이 완료되면 my_function2 코루틴 실행 재개를 요청한다.
import asyncio
#코루틴 함수 설정
async def my_function1():
...
await my_logic() # 코루틴 동작!
async def my_function2(a=4):
....
task = []
for _ in range(a):
task.append(my_function1())
await asyncio.gather(*task)
코루틴과 스레드 간단 차이점
thread | coroutine | |
동시성 | 가능 | 가능 |
비용 | 메모리 추가, 시작, 전환 비용 GIL, 동기화 필요 |
시작 비용 only |
이번 글에서는 동시성, 병렬성의 간단한 사용법에 대해서 다루어봤다. 동기성 프로그래밍에는 익숙하지 않아서 책을 여러 번 읽어야 했었는데, 앞으로는 더욱 능숙하게 다룰 수 있게 되기를. 다음 포스트에서는 스레드와 코루틴 2탄을 적어보려 한다.
'python > 파이썬 코딩의 기술' 카테고리의 다른 글
파이썬 코딩의 기술 리뷰 - 메타클래스와 애트리뷰트 1 (0) | 2022.02.01 |
---|---|
파이썬 코딩의 기술 리뷰 - 클래스, 인터페이스 (0) | 2022.01.16 |
파이썬 코딩의 기술 - 컴프리헨션, 제너레이터 (0) | 2022.01.09 |
파이썬 코딩의 기술 리뷰 - 함수 (0) | 2021.12.19 |
파이썬 코딩의 기술 - 리스트, 딕셔너리 (0) | 2021.12.19 |