그때그때 CS 정리

병렬 처리 몇 가지와 사용처

필만이 2024. 9. 5. 11:38

조사하게된 배경

https://makenow90.tistory.com/66

병렬 처리의 세 가지 주요 개념: 비동기 처리, 멀티스레드, 멀티코어

병렬 처리는 작업의 특성에 따라 적합한 방식이 다릅니다. I/O 병렬 처리는 주로 입출력 대기 시간이 많은 작업에서, 멀티스레드는 경량 작업이나 I/O 바운드 작업에서, 그리고 멀티코어는 CPU 집약적인 작업에서 각각 효율적입니다. 여기에서는 각 방식의 특징과, 적절한 코드 예시를 설명하겠습니다.

1. 비동기 처리

비동기 처리란, 작업이 완료될 때까지 기다리지 않고 다른 작업을 계속 진행하는 방식을 의미합니다.
이는 I/O 작업, 네트워크 요청, 파일 읽기/쓰기대기 시간이 발생하는 작업에서 효율적으로 사용됩니다.
Python에서는 asyncio, aiohttp, **aiofiles**와 같은 라이브러리를 통해 비동기 처리를 구현할 수 있습니다.

특징:

  • 동시성(Concurrency):
    • 여러 작업이 같은 이벤트 루프에서 병렬적으로 실행되는 것처럼 보입니다.
    • 물리적 병렬성(Parallelism)과는 다르며, 단일 스레드에서도 구현할 수 있습니다.
  • I/O 바운드 작업에 적합:
    • 네트워크 호출, 파일 읽기/쓰기, 데이터베이스 쿼리 등 I/O 대기 시간이 긴 작업에 효과적입니다.
    • I/O 작업은 CPU가 작업을 완료하지 않고 대기하는 시간이 많습니다.
    • 네트워크 요청: 서버로 요청을 보낸 후 응답을 기다리는 시간.
    • 파일 읽기/쓰기: 디스크에서 데이터를 로드하거나 저장하는 대기 시간.
    • 비동기 처리는 작업 대기 상태에서 CPU를 유휴 상태로 두지 않고, 다른 작업을 실행
  • 싱글 스레드에서 동작 가능:
    • 비동기 처리는 멀티스레드나 멀티프로세스 환경 없이도 효율적으로 작동합니다.
  • GIL의 영향을 받지 않음:
    • Python의 GIL(Global Interpreter Lock)은 CPU 바운드 작업에서 성능을 제한하지만, 비동기 처리는 GIL의 영향을 거의 받지 않습니다.

비동기 방식을 활용하면 좋은 코딩의 종류

  1. 웹 크롤링 및 데이터 스크래핑
    • 수많은 웹사이트에서 데이터를 동시에 요청하고 응답을 처리하는 작업.
    • 비동기 방식으로 요청 대기 시간을 효율적으로 활용.
  2. API 대량 호출
    • 여러 API 엔드포인트를 동시에 호출하거나, 대규모 병렬 네트워크 요청을 처리하는 작업.
    • 예: 금융 데이터, 날씨 데이터 등 대량의 실시간 데이터를 가져오는 작업.
  3. 대규모 파일 입출력
    • 여러 파일을 동시에 읽거나 쓰는 작업.
    • 데이터 처리 및 분석 파이프라인에서 자주 활용.
  4. 채팅 애플리케이션 및 실시간 스트리밍
    • 다수의 클라이언트와 서버 간 메시지를 주고받는 작업.
    • 예: 채팅, 라이브 스트리밍 애플리케이션에서의 실시간 데이터 처리.
  5. 데이터베이스 비동기 쿼리
    • 대규모 비동기 데이터베이스 요청 처리.
    • 비동기 ORM(예: SQLAlchemy Async)을 사용하여 효율적으로 데이터 읽기/쓰기.

적절한 코드 예시 1: (수천개의)비동기 파일 읽기

import asyncio

async def read_file_async(file):
    async with aiofiles.open(file, 'r') as f:
        contents = await f.read()
    print(f"File {file} read.")

async def main():
    files = ['file1.txt', 'file2.txt', 'file3.txt']
    tasks = [read_file_async(file) for file in files]
    await asyncio.gather(*tasks)

asyncio.run(main())

적절한 코드 예시 2: 비동기 네트워크 요청

import asyncio
import aiohttp

async def fetch_url(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = ['http://example.com', 'http://example.org', 'http://example.net']
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        responses = await asyncio.gather(*tasks)
        print(responses)

asyncio.run(main())

사용 이유:

  • 비동기 처리는 주로 네트워크 요청이나 파일 입출력과 같이 I/O 대기 시간이 큰 작업에서 성능을 향상시킬 수 있습니다. 비동기 방식으로 여러 요청을 동시에 처리하여 효율성을 높일 수 있습니다.

2. 멀티스레드(Multi-Threading)

멀티스레드는 하나의 프로세스 내에서 여러 스레드를 생성하여 작업을 병렬로 수행하는 방식입니다. 각 스레드는 동일한 메모리 공간을 공유하면서 동시에 실행됩니다.

특징:

  • 스레드(Thread): 스레드는 프로세스 내의 실행 흐름으로, 여러 스레드가 하나의 프로세스 내에서 병렬로 작업을 수행합니다. 스레드는 경량 프로세스라고도 하며, 프로세스보다 생성과 관리가 더 간단하고 빠릅니다.
  • I/O 바운드 작업에 적합: CPU가 바쁘지 않고 I/O 작업을 대기하는 시간이 많을 때, 여러 스레드를 사용해 대기 시간을 줄일 수 있습니다. 예를 들어, 파일 읽기, 네트워크 요청 등을 동시에 여러 스레드에서 처리하면, 작업의 전반적인 대기 시간이 줄어들게 됩니다.
  • GIL(Global Interpreter Lock): Python에서 멀티스레딩은 CPU 바운드 작업에 대한 성능 향상이 제한적일 수 있습니다. Python의 GIL로 인해 한 번에 하나의 스레드만 Python 바이트코드를 실행할 수 있으므로, CPU 집약적인 작업에서는 병목이 발생할 수 있습니다.

멀티스레드를 활용하면 좋은 코딩의 종류

  1. 백그라운드 작업 처리
    • UI 애플리케이션에서 사용자 인터페이스를 멈추지 않고 백그라운드에서 작업을 수행.
    • 예: 파일 다운로드, 이미지 처리 등.
  2. 경량 데이터 처리
    • 독립적인 데이터 처리 작업이 많고, 작업 간 간섭이 적은 경우.
    • 예: 로그 파일 분석, 실시간 데이터 필터링.
  3. 서버 요청 처리
    • 다중 클라이언트 요청을 처리하는 서버에서 각 요청을 독립적인 스레드로 처리.
    • 예: 채팅 서버, 멀티스레드 웹 서버.
  4. 멀티태스킹 애플리케이션
    • 여러 작업을 동시에 실행하는 프로그램.
    • 예: 동시에 센서 데이터를 읽고 저장하거나, 사용자 입력을 처리하는 임베디드 시스템.
  5. 멀티미디어 처리
    • 동영상 재생, 오디오 스트리밍, 이미지 렌더링 등 실시간 작업에서 멀티스레드로 성능 향상.
    • 예: 프레임 처리, 필터 적용.

 

적절한 코드 예시 1: 파일 다운로드

from threading import Thread
import requests

def download_file(url):
    response = requests.get(url)
    filename = url.split("/")[-1]
    with open(filename, 'wb') as f:
        f.write(response.content)
    print(f"Downloaded {filename}")

urls = ['http://example.com/file1', 'http://example.com/file2', 'http://example.com/file3']
threads = []

for url in urls:
    thread = Thread(target=download_file, args=(url,))
    thread.start()
    threads.append(thread)

for thread in threads:
    thread.join()

멀티스레드의 장점:

  • I/O 바운드 작업에 매우 효율적: 네트워크 요청, 파일 읽기/쓰기와 같은 I/O 작업에서는 멀티스레딩을 통해 여러 작업을 동시에 처리할 수 있습니다.
  • 경량 프로세싱: 스레드 간 문맥 교환이 프로세스보다 가볍기 때문에 오버헤드가 적습니다.

단점:

  • GIL의 제약(Python): Python의 GIL로 인해, CPU 바운드 작업에서는 성능이 제한될 수 있습니다.
  • 스레드 관리의 복잡성: 스레드가 많아질수록 동기화 문제(예: 데이터 레이스, 데드락) 등의 복잡한 상황이 발생할 수 있습니다.

적절한 코드 예시 2: 로그 파일 모니터링

from threading import Thread

def monitor_log(file):
    with open(file, 'r') as f:
        while True:
            line = f.readline()
            if line:
                print(f"{file}: {line.strip()}")

log_files = ['log1.txt', 'log2.txt', 'log3.txt']
threads = []

for file in log_files:
    thread = Thread(target=monitor_log, args=(file,))
    thread.start()
    threads.append(thread)

for thread in threads:
    thread.join()

사용 이유:

  • 멀티스레드는 네트워크 요청, 파일 읽기/쓰기와 같이 I/O 대기 시간이 많은 작업에서 성능을 높일 수 있습니다. 각 스레드가 독립적으로 실행되어 I/O 대기 중에도 다른 작업을 수행할 수 있기 때문에 효율적입니다.

3. 멀티프로세스

멀티프로세스는 하나의 프로그램에서 여러 프로세스를 병렬로 실행하여 작업을 처리하는 방식입니다.
프로세스(Process)는 독립된 실행 단위로, 각각의 프로세스는 독립적인 메모리 공간을 가지고 동작함

특징:

 

1) 프로세스란?

  • 프로세스는 프로그램의 실행 인스턴스로, 운영 체제에서 관리되는 독립적인 실행 단위입니다.
  • 각각의 프로세스는 고유한 메모리 공간을 할당받아 독립적으로 실행됩니다.
  • 운영 체제의 스케줄러가 프로세스를 병렬로 실행합니다.

2) 멀티프로세스의 특징

장점

  1. GIL 제약 없음:
    • Python의 GIL은 멀티스레드의 병렬성을 제한하지만, 멀티프로세스는 이를 우회하여 진정한 병렬 처리를 구현할 수 있습니다.
  2. 독립된 메모리 공간:
    • 프로세스 간 메모리가 독립적이므로, 하나의 프로세스가 종료되거나 충돌해도 다른 프로세스에 영향을 주지 않습니다.
  3. 멀티코어 활용:
    • 프로세스는 운영 체제가 제공하는 멀티코어를 활용하여 병렬로 실행되므로, CPU 자원을 최대한 활용할 수 있습니다.

단점

  1. 메모리 사용량 증가:
    • 각 프로세스가 독립적인 메모리 공간을 가지므로, 멀티스레드보다 메모리 사용량이 많습니다.
  2. 통신 비용(IPC):
    • 프로세스 간 데이터 공유나 통신이 필요할 경우 **Inter-Process Communication(IPC)**을 사용해야 하며, 이로 인해 오버헤드가 발생합니다.
    • 그럼에도 IPC를 사용 하는 예: 대규모 데이터 처리에서 중간 결과 병합, 작업 상태 공유
    • 데이터 교환과 동기화가 필요하다면, Queue, Pipe( 양방향 데이터 전달이 가능 ), Shared Memory( 프로세스 간 메모리를 공유하여 데이터를 전달 ) 와 같은 IPC 방식을 활용.
  3. 프로세스 생성 비용:
    • 프로세스를 생성하는 데 더 많은 리소스와 시간이 필요합니다.

 

적절한 코드 예시 1: 데이터 분석 병렬 계산

from multiprocessing import Pool
import numpy as np

def compute_square(number):
    return number ** 2

numbers = np.arange(1000000)

if __name__ == '__main__':
    with Pool() as pool:
        result = pool.map(compute_square, numbers)
    print(result)

적절한 코드 예시 2: 이미지 처리

from multiprocessing import Pool
from PIL import Image
import os

def process_image(image_file):
    img = Image.open(image_file)
    img = img.rotate(90)  # 이미지 회전
    img.save(f"rotated_{os.path.basename(image_file)}")
    return f"{image_file} processed."

image_files = ['image1.jpg', 'image2.jpg', 'image3.jpg']

if __name__ == '__main__':
    with Pool() as pool:
        results = pool.map(process_image, image_files)
    print(results)

 

멀티프로세스를 활용하면 좋은 코딩의 종류

  1. CPU 집약적인 작업
    • 복잡한 계산이나 대규모 데이터 처리를 병렬로 실행.
    • 예: 머신 러닝 모델 훈련, 이미지/비디오 처리, 시뮬레이션 연산.
  2. 데이터 파이프라인 병렬 처리
    • 데이터 분석 작업에서 대량의 데이터를 여러 프로세스에서 병렬로 처리.
    • 예: 대규모 로그 분석, ETL(Extract, Transform, Load) 작업.
  3. 웹 크롤러와 스크래퍼의 대규모 병렬 처리
    • 여러 프로세스에서 독립적으로 웹 페이지를 다운로드하거나 크롤링.
    • 비동기 방식보다 프로세스를 활용하면 병렬성에서 이점을 얻음.
  4. 멀티유저 서버
    • 고성능 서버에서 다수의 클라이언트 요청을 병렬로 처리.
    • 예: HTTP 요청 처리, 게임 서버의 클라이언트별 독립된 세션 관리.
  5. 비동기 I/O와 CPU 작업 결합
    • 비동기적으로 데이터를 읽어온 후, 데이터를 병렬로 처리.
    • 예: 네트워크 데이터를 수집한 후 여러 프로세스에서 분석 및 저장.

정리:

  • I/O 병렬 처리: 네트워크 요청, 파일 읽기/쓰기와 같은 I/O 바운드 작업에서 비동기 I/O나 멀티스레딩을 통해 성능을 향상시킵니다. 예: 비동기 파일 읽기, 비동기 네트워크 요청.
  • 멀티스레드: I/O 대기 시간이 많은 작업에서 여러 스레드를 통해 동시에 처리하여 대기 시간을 최소화합니다. 예: 파일 다운로드, 로그 파일 모니터링.
  • 멀티코어: CPU 집약적인 작업에서 여러 코어를 활용해 병렬 처리를 통해 성능을 극대화합니다. 예: 데이터 분석 병렬 계산, 이미지 처리.

병렬 처리 방식 비교 (I/O 병렬 처리, 멀티스레드, 멀티코어)

특징 비동기 I/O 멀티스레드 멀티프로세스
주요 사용 환경 I/O 바운드 작업 (네트워크 요청, 파일 읽기/쓰기) I/O 바운드 작업 (네트워크 요청, 파일 읽기/쓰기) CPU 집약적인 작업 (데이터 분석, 이미지 처리, 계산 작업)
처리 방식 이벤트 루프 기반 비동기 실행 스레드 기반 동시 실행 멀티프로세싱을 통한 프로세스 간 병렬 실행
동시성 단일 스레드에서 비동기적으로 여러 작업을 처리 여러 스레드가 동시에 실행되며, 스레드마다 독립적인 실행 흐름을 가짐 여러 코어에서 각각 독립된 프로세스를 병렬로 실행
GIL 문제 GIL과 무관, 단일 스레드에서 비동기 작업 가능 I/O 작업에서는 GIL 문제가 거의 없음, CPU 집약 작업에서는 성능 제한이 있음 GIL과 무관, 각 프로세스가 독립적으로 실행됨
메모리 사용량 낮음 (단일 스레드 사용) 스레드 간 메모리 공유 가능, 비교적 낮음 프로세스 간 메모리 분리, 높은 메모리 사용량 가능
오버헤드 스레드 오버헤드 없음, 이벤트 루프 관리 오버헤드 적음 스레드 생성과 컨텍스트 스위칭에 따른 오버헤드 존재 프로세스 생성과 관리에 따른 오버헤드가 큼
복잡성 비동기 방식이므로 스레드 동기화 문제 없음 스레드 간 동기화 필요, 데드락, 레이스 컨디션 발생 가능 프로세스 간 통신 필요 (IPC), 복잡한 데이터 공유
주요 Python 모듈 asyncio, aiohttp threading multiprocessing, concurrent.futures
사용 예 - 네트워크 요청 처리 - 비동기 파일 읽기/쓰기 - 파일 다운로드 - 로그 파일 모니터링 - 데이터 분석 병렬 계산 - 이미지 처리