동시성과 병렬성 프로그래밍
서론
컴퓨터 프로그래밍 세계에서 "동시성(Concurrency)"과 "병렬성(Parallelism)"은 성능 최적화를 위해 언급되는 두가지 개념이다.
동시성 프로그래밍이란?
동시성 프로그래밍은 여러 작업을 동시에 진행하는 것처럼 보이게 하는 기술이다.
실제로는 한 번에 하나의 작업만 처리하면서, 작업들 사이를 빠르게 전환함으로써 여러 작업을 처리하는 것처럼 보인다.
마치 요리를 하면서 전화 통화를 하는 것처럼, 실제로는 한 번에 한 작업에만 집중하지만, 빠르게 작업을 전환하며 여러 일을 처리하는 것처럼 보이게 한다.
동시성은 주로 데이터를 기다리는 작업(I/O 바운드 작업)에 유용하며,
컴퓨터가 네트워크 요청이나 디스크 I/O 작업을 기다리는 동안 다른 작업을 진행할 수 있게 한다.
- I/O 바운드 : I: 인풋 O : 아웃풋 프로그램이 실행할때 실행속도가 I/O에 의해 제한됨
import requests
def io_bound_func():
result = requests.get("https://google.com")
return result
if __name__ == "__main__": # 바운드 된다는거 확인하기 위해 10번돌림
for i in range(10):
result = io_bound_func()
print(result)
보면 CPU가 연산하는게 아니라 google.com(네트워크)로 요청하는데
요청이 쌓이면서 멈추는거 I/O 바운드
병렬성 프로그래밍이란?
병렬성 프로그래밍은 여러 작업을 실제로 동시에 실행하는 기술.
이는 여러 코어 또는 프로세서를 사용하여 다양한 작업을 동시에 처리한다.
예를 들자면, 마치 여러 사람이 함께 집을 청소하는 것과 비슷하다.
한 사람은 거실을, 다른 사람은 부엌을, 또 다른 사람은 욕실을 청소하면 여러 작업이 동시에 수행되어 전체 작업 시간이 크게 단축된다.
병렬성은 계산 작업이 많은 작업(CPU 바운드 작업)에 특히 유용하다.
- CPU 바운드 : CPU가 연산을 너무 많이해서 CPU가 프로그램 실행을 막는 것.
동시성 vs 병렬성: 선택의 문제
병렬성 프로그래밍이 여러 작업을 실제로 동시에 처리한다는 점에서 더 빠른 성능을 제공할 것 같지만, 실제로는 동시성과 병렬성 각각이 서로 다른 상황에서 더 적합할 수 있다. 선택은 작업의 성격, 자원의 가용성, 그리고 구현의 복잡성 등 여러 요소를 고려하여 이루어져야 한다.
예를들어 일부 작업은 시작하기 전에 이전 작업이 완료되어야 하는 종속성이 있을 수도 있고
잘못 관리하면 deadlock이나 race condition 같은 문제가 발생할 수 있기 때문이다.
- Deadlock: 서로 다른 프로세스가 상대방의 자원을 기다리며 무한히 대기하는 상태
- Race Condition: 여러 프로세스가 동시에 같은 데이터를 변경하려고 해서 결과가 예측 불가능해지는 상태
예시:
동기식 처리 예시 : 도서관 책 대여
import time
def borrow_book(name, read_time):
print(f"{name} 책 대여 시작...")
time.sleep(read_time) # 책을 읽는데 걸리는 시간을 시뮬레이션
print(f"{name} 책 읽기 완료, {read_time}시간 소요...")
print(f"{name} 책 반납 완료")
def main():
borrow_book("책 A", 2) # 2시간 동안 책 A를 읽는다
borrow_book("책 B", 2) # 이어서 2시간 동안 책 B를 읽는다
borrow_book("책 C", 2) # 마지막으로 2시간 동안 책 C를 읽는다
if __name__ == "__main__":
start = time.time()
main() # None
end = time.time()
print(f"총 소요 시간: {end - start}초")

비동기식 처리 예시 : 도서관 책 대여
# 비동기식 처리 예시: 도서관 책 대여
import time
import asyncio
async def borrow_book(name, read_time):
print(f"{name} 책 대여 시작...")
await asyncio.sleep(read_time) # 비동기적으로 책을 읽는 시간을 기다린다
print(f"{name} 책 읽기 완료, {read_time}시간 소요...")
print(f"{name} 책 반납 완료")
return read_time
async def main():
result = await asyncio.gather(
borrow_book("책 A", 2), # 2시간 동안 책 A를 읽는다
borrow_book("책 B", 2), # 동시에 2시간 동안 책 B를 읽는다
borrow_book("책 C", 2), # 동시에 2시간 동안 책 C를 읽는다
)
print(f"각 책 읽는데 걸린 시간: {result}")
if __name__ == "__main__":
start = time.time()
asyncio.run(main())
end = time.time()
print(f"총 소요 시간: {end - start}초")

결론
- 동시성 프로그래밍은 자원 활용이 중요하고, 응답성 향상이 필요한 상황에서 유리
- 병렬성 프로그래밍은 계산 속도를 극대화하고, 대규모 데이터 처리가 필요한 상황에서 더 적합
즉, 코드가 동기적으로 동작한다. -> 코드가 반드시 작성된 순서 그대로 실행된다.
코드가 비동기적으로 동작한다. -> 코드가 반드시 작성된 순서 그대로 실행되는 것이 아니다.
적합한 방법을 선택하는 것이 중요
때로는 두 가지 접근 방식을 조합하여 사용하는 것이 좋을 수도 있다.
성능 최적화를 위해 이러한 개념들을 이해하고 적절히 활용하는 것이 핵심
'IT > Python' 카테고리의 다른 글
Python - 알고리즘 (선형검색,이진검색) (1) | 2024.02.26 |
---|---|
Python - 자료구조 (0) | 2024.02.22 |
[Python] __name__ (1) | 2024.02.14 |
[Python] 지역변수와 전역변수 (0) | 2024.02.13 |
[Python] 함수에서 return 사용하는 이유 (0) | 2024.02.13 |