
Multi-Process는 독립적인 메모리 공간을 가짐
Multi-Thread는 하나의 Process의 작업을 나누어 하므로 자원을 공유



읽어보면 좋을 내용
각각의 Process와 Thread들은 CPU에 의해 스케줄링됨
작업 전환에는 Context Switching 시간이 포함
Race condition

Race condition의 문제점
멀티 스레드에서의 공유 영역에는 대표적으로 데이터 영역과 힙 영역이 있습니다.
반대로 공유하지 않는 영역은 스택 영역입니다.
멀티 스레드 프로그래밍의 장점은 공유 영역을 여러 일들을 병렬로 처리 할 수 있는 것도 있지만, 자원을 공유하면서 생성과 관리의 중복성을 최소화 할 수 있는 장점도 있습니다.
하지만 이런 공유 영역이 양날의 검처럼 문제도 발생 할 위험이 있는 것입니다.
개념보다는 문제점을 직접 나열해보니 Race condition을 알아야 하는 이유가 좀 더 명확해지고, 멀티 스레드 프로그래밍이 어렵다고 하는 이유를 좀 더 알게 되는 것 같습니다.
상호 배제(Mutual Exclusion, Mutex)
동시 프로그래밍에서 공유 불가능한 자원의 동시 사용을 피하기 위해 사용되는 알고리즘으로, 임계 구역(critical section)으로 불리는 코드 영역에 의해 구현된다.
- 공유된 자원의 데이터를 여러 스레드가 접근하는 것을 막는 방법
- 공유된 자원은 한 번에 한 프로세스(스레드)만이 사용할 수 있어야 함
- key에 해당하는 어떤 오브젝트(공유자원에 접근하기 위해 필요한 어떤 오브젝트)가 있으며 이 오브젝트를 소유한 (쓰레드, 프로세스) 만이 공유자원에 접근할 수 있음
임계 구역(Critical Section)
공유변수 영역은 병렬컴퓨팅에서 둘 이상의 스레드가 동시에 접근해서 안되는 공유 자원(자료 구조 또는 장치)을 접근하는 코드의 일부를 말한다.
- 간단하게 정리하면 병렬 컴퓨팅과 멀티스레드 프로그래밍에서 상호 배제를 적용하는 부분을 가리키는 용어
- 임계 구역을 효과적으로 사용하는 것은 Race condition과 같은 동시성 문제를 해결하기 위해 중요
- 임계 구역에 접근하기 전에 락(Lock)이나 세마포어(Semaphore)와 같은 동기화 메커니즘을 사용하여 한 번에 하나의 실행 흐름만이 임계구역에 들어갈 수 있도록 함
CPython?
C파이썬(CPython)은 파이썬 프로그래밍 언어의 참조 구현체이다. C와 파이썬으로 작성된 C파이썬은 이 언어에 가장 널리 사용되는 기본 구현체이다.
C파이썬은 인터프리트 과정 이전에 파이썬 코드를 바이트코드로 컴파일하기 때문에 인터프리터이기도 하고 컴파일러이기도 하다. C를 포함한 여러 언어의 외부 함수 인터페이스를 보유하고 있으며 여기서 파이썬 외의 언어로 바인딩을 명시적으로 작성해야 한다.
- Python 그 자체
- Python 소프트웨어 재단에서 만드는 표준 Python 구현체
- C로 구현되어 있음
cf. pypy : Python Compiler를 Python으로 작성한 Python 구현체
import threading
import time
# CPU-bound 작업: 간단한 계산 작업을 시뮬레이션
def cpu_bound_task(start, end, result):
total = 0
for i in range(start, end):
total += i
result.append(total)
if __name__ == "__main__":
# CPU-bound 작업을 위한 범위 및 스레드 개수 설정
total_numbers = 10**7
num_threads = 2
chunk_size = total_numbers // num_threads
# 단일 스레드로 작업 실행
start_time_single = time.time()
cpu_bound_task(1, total_numbers + 1, [])
end_time_single = time.time()
execution_time_single = end_time_single - start_time_single
# 멀티스레딩으로 작업 실행
start_time_multi = time.time()
result_list = []
threads = []
for i in range(num_threads):
start = i * chunk_size + 1
# 실행 thread 개수만큼 데이터 나누기
end = (i + 1) * chunk_size + 1 if i < num_threads - 1 else total_numbers + 1
thread = threading.Thread(target=cpu_bound_task, args=(start, end, result_list))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
total_result = sum(result_list)
end_time_multi = time.time()
execution_time_multi = end_time_multi - start_time_multi
print(f"단일 스레드 실행 시간: {execution_time_single:.4f} 초")
print(f"멀티스레딩 실행 시간: {execution_time_multi:.4f} 초")
# 단일 스레드 실행 시간: 0.3817 초
# 멀티스레딩 실행 시간: 0.3829 초
import multiprocessing
from queue import Queue
import time
# CPU-bound 작업: 간단한 계산 작업을 시뮬레이션
def cpu_bound_task(start, end, result):
total = 0
for i in range(start, end):
total += i
result.put(total)
if __name__ == "__main__":
# CPU-bound 작업을 위한 범위 및 프로세스 개수 설정
total_numbers = 10**7
num_processes = 4
chunk_size = total_numbers // num_processes
# 단일 프로세스로 작업 실행
start_time_single = time.time()
single_result = Queue()
cpu_bound_task(1, total_numbers + 1, single_result)
end_time_single = time.time()
execution_time_single = end_time_single - start_time_single
# 멀티프로세스로 작업 실행
start_time_multi = time.time()
result_queue = multiprocessing.Queue()
processes = []
for i in range(num_processes):
start = i * chunk_size + 1
# 실행 process 개수만큼 데이터 나누기
end = (i + 1) * chunk_size + 1 if i < num_processes - 1 else total_numbers + 1
process = multiprocessing.Process(target=cpu_bound_task, args=(start, end, result_queue))
processes.append(process)
process.start()
for process in processes:
process.join()
total_result = sum([result_queue.get() for _ in range(num_processes)])
end_time_multi = time.time()
execution_time_multi = end_time_multi - start_time_multi
print(f"단일 프로세스 실행 시간: {execution_time_single:.4f} 초")
print(f"멀티프로세스 실행 시간: {execution_time_multi:.4f} 초")
# 단일 프로세스 실행 시간: 0.3874 초
# 멀티프로세스 실행 시간: 0.2981 초