본문 바로가기
Python

I/O bound and CPU bound, 동시성

by 쿠리의일상 2024. 3. 12.

Python 으로 백엔드를 제작 중이다. 그리고 python 으로 특정 기능을 구현하고 그 기능을 백엔드로 연동시켜주고자 할 때 혹시 필요한가 싶어서 이거저거 레퍼런스를 서칭하고 있다.

그중에서도 threading 관련 하여 정리가 필요함을 느꼈다.

https://youtu.be/qnVKEwjG_gM?si=gGXXwBCbK05XrGp_

 

CPU (Central Processing Unit)

프로세스 명령어를 해석하고 실행하는 장치

I/O

  • 파일을 읽거나 쓰기
  • 네트워크와 데이터를 주고 받기 (네트워크 통신)
  • 입출력 장치(키보드, 모니터 등)와 데이터를 주거나 받기
  • DB 접근

 

Burst 
어떤 현상이 짧은 시간 안에 집중적으로 일어나는 것
-> CPU Burst: 프로세스 가 CPU에서 한번에 연속적으로 실행되는 시간
-> IO Burst: 프로세스가 IO작업을 요청하고 결과를 기다리는 시간

 

프로세스는 CPU burst 와 IO burst 의 연속이다

보통의 프로세스는 CPU 작업을 할 때 길게 하지 않고 짧게(8ms) 처리하게 된다. 그래서 상대적으로 IO 작업은 긴 것이다.


 

CPU bound 프로세스

= CPU burst 가 많은 프로세스

동영상 편집 프로그램이나 머신러닝/딥러닝 프로그램 등은 CPU 연산이 많다. 그래서 상대적으로 낮은 성능이지만 많은 개수의 코어가 존재하는 GPU 연산까지 하는 경우가 대다수이다. 그러므로 CPU 성능에 따라 작업 속도가 결정되는 특징이 있다.

CPU bound 프로그램에서 적절한 스레드의 수는 CPU core의 + 1개

예를 들어 듀얼 코어 CPU에서 CPU bound 프로그램을 만들고자 한다면 스레드를 몇 개로 두는 것이 효율적일까?

컨텍스트 스위칭을 최소화 하는 것이 오버헤드를 줄일 수 있기 때문에 스레드 개수를 CPU 의 코어 개수와 맞추는 것이 좋고, 추가적으로 필요하다면 +1개로 지정하는 것을 권장한다고 한다.

 

IO bound 프로세스 

= IO burst 가 많은 프로세스

네트워크 간 통신을 사용하는 일반적인 백엔드 API 서버가 해당된다.  작업에 의한 병목 현상으로 작업 속도가 결정된다.

IO bound 프로그램은 상황에 맞춰 적절한 스레드 수를 찾아야 한다

예를 들어, API 서버가 thread per request 방식이라고 가정하면, 몇 개의 스레드들을 미리 만들어 놓을지는 요청이 얼마나 오는지, 스레드의 처리 범위 등 여러 상황에 따라 달라지게 될 것이다.

thread per request
api 요청 마다 전담 thread 를 할당하여 요청을 처리하는 방식

 


경쟁 조건

멀티 스레딩 기반의 백엔드 API를 만들 때 중요한 개념 중 하나이다. 쓰레드를 여러 개 두고 하나의 작업을 한다면 중간에 스레드 컨텍스트 스위칭에 의한 데이터 손실이 일어날 가능성이 있다.

즉 경쟁 조건이란 여러 프로세스, 스레드가 동시에 같은 데이터를 조작할 때 타이밍이나 접근 순서에 따라 결과가 달라질 수 있는 상황을 의미한다. 이를 위해선 동기화가 필요하다.

 

동기화

여러 프로세스, 스레드를 동시에 실행해도 공유 데이터의 일관성을 유지하는 것을 의미한다. 

 

단순하게 멀티 스레딩 환경에서 컨텍스트 스위칭을 막으면 되는 것은 현실적으로 불가능하다. 그러므로 스레드 하나로만 가능하게끔 제한을 주는 방식을 사용할 필요가 있다. 

임계 영역

공유 데이터의 일관성을 보장하기 위해 하나의 프로세스, 스레드만 진입해서 실행 가능하게 만든 영역이다. 

do {

   entry section
    	critical section
    end section


} while (true)
  1. entry section: 임계 영역에 진입 전 요건을 확인하는 영역
  2. critical section: 임계 영역
  3. end section: 임계 영역의 작업이 이후 처리
임계영역 문제를 해결하기 위한 조건
  1. mutual exclusion(상호 배제): 한번에 하나의 프로세스, 스레드만 임계 영역에서 실행될 것
  2. progress: 임계 영역이 비어있을 때 두개 이상의 프로세스, 스레드가 진입하길 원할 때 하나만 진행
  3. bounded waiting(한정된 대기): 프로세스나 스레드가 무한정 임계 영역에 들어가길 기다리면 안됨

위 3가지를 모두 충족해줘야 임계 영역 문제의 해결책이 된다고 한다.

 


파이썬에서의 동시성

  • 태스크: asyncio
  • 쓰레드: threading
  • 프로세스: multiprocessing

threading 은 하나의 프로세스를 사용하므로 한번에 하나씩 실행하게 된다. 동시에 실행되는 것처럼 보이는 것은 운영체제가 각 쓰레드를 컨텍스트 스위칭을 통해 선점형 멀티태스킹을 수행하기 때문에 가능한 것이다. 병렬 처리가 실제로 처리되는 것은 multiprocessing 을 사용한 방식으로 이는 새로운 프로세스를 생성한다. asyncio 방식은 각 태스크가 스위칭될지 명시해줘야 하는 협력식 멀티태스킹 방식을 이용한다.

 

thread

import threading

lock = threading.Lock()
// ...

with lock:
	// 동시에 실행되지 않게끔 해줄 때

 

import threading

lock = threading.Lock()


lock.acquire()
// 동시에 접근 불가한 내용
lock.release() // Lock 반환

 

 

일단은 여기까지.. 어려운 개념이라 매번 봐도 어렵다ㅜㅜ

'Python' 카테고리의 다른 글

Anaconda 처음 써보기  (0) 2023.10.20
pynetdicom 에러 핸들링 - Association Aborted  (0) 2023.10.16