일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 강화학습
- 추천시스템
- tool_binding
- langgraph
- add_subgraph
- rag
- tool_calls
- rl
- update_state
- Python
- REACT
- toolnode
- 밑바닥부터 시작하는 딥러닝
- conditional_edges
- 강화학습의 수학적 기초와 알고리듬 이해
- humannode
- chat_history
- Ai
- conditional_edge
- 강화학습의 수학적 기초와 알고리듬의 이해
- lcel
- langgrpah
- 밑바닥부터시작하는딥러닝 #딥러닝 #머신러닝 #신경망
- pinecone
- summarize_chat_history
- subgraph
- human-in-the-loop
- RecSys
- LangChain
- removemessage
- Today
- Total
타임트리
[Python] Multi-Threading, Multi-Processing, GIL 본문
앞선 게시물들에서 살펴본 것처럼 동시성이란 여러 작업을 스위칭하며 처리하는 것을 의미한다. 따라서 동시성 프로그램을 사용하면 네트워크 I/O와 같이 요청/응답 과정에서 효율적으로 코드를 설계할 수 있다. 그런데 여기서 생각해볼 점이 있다. 기존처럼 순차적으로 진행했던 동기적 코드보다 항상 비동기적 코드가 좋을까?
정답은 그렇지 않다는 것이다. 예를 들어, CPU 연산이 많은 프로그램을 생각해보자. 이러한 경우 하나의 CPU가 쭉 계산하는 것과 여러 스레드에 동시성으로 작업하는 것과 차이가 없을 것이다. 오히려 여러 excutor로 작업을 할당하는 연산이 추가되어 더 오래 걸릴 것이라 예상할 수 있다. 이러한 경우에는 동시성 보다는 병렬성으로 작업을 수행해야 속도를 향상시킬 수 있다.
그런데 파이썬은 다른 언어와는 달리, 멀티 스레드로 병렬성 프로그래밍을 할 수 없다. 즉, 하나의 CPU가 스레드들한테 작업을 병렬적으로 수행시킬 수 없다. 그 이유는 멀티 스레딩이 메모리를 공유함으로써 하나의 스레드 오류가 다른 스레드까지 영향을 미치는 단점을 방지하기 위해 GIL을 도입했기 때문이다.
GIL이란 Global Interpreter Lock의 약자로, 전역 인터프리터 잠금을 의미한다. 즉, 한 번에 한 개의 스레드만 유지하는 락을 말하는데 이는 동시에 여러 개의 스레드를 사용할 수 없음(병렬X)을 의미한다. 이러한 방법을 사용하여 멀티 스레딩의 위험으로부터 프로그램 오류를 방지한다. 그리고 이 때문에 파이썬에서는 스레드로 병렬성 연산을 수행하지 못한다. (대신, 자장면 배달 예시처럼 파이썬에서는 멀티 스레드로 동시성을 활용해서 I/O 바운드에서 유용하게 사용할 수 있다)
파이썬 멀티 스레딩
동시성으로 I/O 바운드에서는 유용하게 사용
단, CPU 바운드 코드에서는 GIL로 인해 병렬 연산 불가
반면, 멀티 프로세싱의 경우, 하나의 프로세스를 하나의 자식 프로세스로 복제를 해서 진행함으로 독립성이 유지된다. 따라서 파이썬에서 병렬적 작업을 수행하기 위해서는 멀티 프로세싱을 이용해야 한다. (단, 멀티 프로세싱은 메모리를 공유하지 않아 통신을 해야 하는데, 이때 발생하는 비용이 크다는 단점 존재)
import time
import os
import threading
from concurrent.futures import ThreadPoolExecutor # 멀티 스레드
from concurrent.futures import ProcessPoolExcutor # 멀티 프로세스
nums = [50, 60, 42]
def cpu_bound(num):
print("{} process | {} thread, {}".format(os.getpid(), threading.get_ident() thread, num))
total = 1
for i in range(1, num+1):
for j in range(1, num+1):
for k in range(1, num+1):
total *= i * j * k
def main_sync():
for num in nums:
cpu_bound(num)
def main_multi_thread():
executor = ThreadPoolExecutor(max_worker=10)
result = list(executor.map(cpu_bound, nums))
def main_multi_process():
executor = ProcessPoolExcutor(max_worker=10)
result = list(executor.map(cpu_bound, nums))
위 코드에서 cpu_bound()
는 단순 곱셈 연산만 존재하는 cpu 연산만 필요한 함수다. 이러한 경우 굳이 동시성을 사용할 필요가 없다. 오히려 멀티 스레드로 동시성 작업을 수행하면 더 오래 걸릴 거라고 예상되는데 진짜 그런지 확인해 보자.
main_sync()
: 하나의 프로세스 하나의 스레드 (총 소요시간: 9.04)
14540 process | 23672 thread
14540 process | 23672 thread
14540 process | 23672 thread
총 소요시간: 9.043886661529541초
main_multi_thread()
: 하나의 프로세스 3개의 스레드 (총 소요시간: 9.09)
31936 process | 31612 thread, 50
31936 process | 30992 thread, 60
31936 process | 31972 thread, 42
총 소요시간: 9.096699953079224초
main_multi_process()
: 3개의 프로세스 (총 소요시간 7.6) - 작업 수가 많으면 각 프로세스도 동시성 수행 가능
30508 process | 12576 thread, 50
30244 process | 6900 thread, 60
31068 process | 30056 thread, 42
총 소요시간: 7.623342037200928초
정리하면, 다음과 같다.
- 동시성을 사용하면 네트워크 I/O와 같이 요청 - 응답 과정에서 효율적으로 코드를 설계할 수 있지만, CPU 연산 작업 등에서는 동시성이 오히려 속도에 악영향을 미칠 수 있다.
- 파이썬에서는 멀티 스레드로 병렬 연산이 불가능하다.
- CPU bound 상황에서는 멀티 프로세싱을 사용하는 것이 좋다.
---
참고: 인프런(파이썬 동시성 프로그래밍 : 데이터 수집부터 웹 개발까지 (feat. FastAPI))
'Today I Learned > 동시성 프로그래밍' 카테고리의 다른 글
[Python] 동시성(concurrency)와 병렬성(parallelism) (0) | 2024.05.26 |
---|---|
[Python] 프로그램, 프로세스와 스레드 (0) | 2024.05.26 |
[Python] 파이썬 코루틴 (0) | 2024.05.26 |
[Python] 동기와 비동기 (0) | 2024.05.26 |
[Python] 바운드와 블로킹 (0) | 2024.05.26 |