블로킹 I/O의 경우 스레드를 사용하고 병렬성을 피하라

매일 공부(ML)·2022년 7월 25일
0

이어드림

목록 보기
106/146
"""
파이썬을 활용하여 컴퓨터의 모든 CPU코어를 활용할 수 있도록 한다.
이때, 다중 스레드를 사용해서 계산 수행하는 것이 타당하다
"""

def factorize(number):
    for i in range(1, number+1):
        if number %i ==0:
            yield

from threading import Thread

class FactorizeThread(Thread):
    def __init__(self, number):
        super().__init__()
        self.number = number

    def run(self):
        self.factors = list(factorize(self.number))
#수마다 스레드를 시작해서 병렬로 인수 찾기

import time

numbers = [2139079,1214759,1516637,1852285]
start = time.time()

threads = []
for number in numbers:
    thread = FactorizeThread(number)
    thread.start()
    threads.append(thread)

for thread in threads:
    thread.join()

end = time.time()
delta = end - start
print(f'총 {delta:.3f} 초 걸림')

신기하게도, CPU가 많아도 스레드를 사용하면 속도가 느려지기도 한다. 왜냐하면, 표준 CPython 인터프리터에서 프로그램을 사용할 때 GIL(락 충돌과 스케줄링 부가비용)이 미치는 영향을 보여준다.

위와 같은 문제가 있어도 파이썬이 스레드를 지원하는 이유

  • 다중 스레드를 사용하면 프로그램이 동시에 여러 일을 하는 것처럼 보이게 만들기 쉽다.(동시성 작업)

  • 블로킹 I/O를 다루기 위해서이다.

    • 특정 시스템 콜을 사용할 때 일어난다.
#속도는 잡았을지라도 실행하는 동안 다른 프로그램 활용을 전혀 못함
import select
import socket

def slow_systemcall():
    select.select([socket.socket()], [], [], 0.1)

start = time.time()

for _ in range(5):
    slow_systemcall()

end = time.time()
delta = end - start

print(f'총 {delta:.3f} 초 걸림')
#총 0.002 초 걸림

#여러 스레드에서 따로따로 호출

start = time.time()
threads = []
for _ in range(5):
    thread = Thread(target=slow_systemcall)
    thread.start()
    threads.append(thread)

병렬화한 버전은 순차적 실행보다 시간이 1/5로 줄어든다.

GIL은 파이썬 프로그램이 병렬로 실행하는 것을 막지는 못하지만 시스템 콜에는 영향을 끼칠 수 없다.


Summary

  • 파이썬 스레드는 GIL(전역 인터프리터 락)으로 인해서 다중 CPU코어에서 병렬로 실행될 수 없다.

  • GIL이 있음에도 불구하고 파이썬 스레드는 여전히 유용하고, 스레드를 사용하면 여러 일을 동시에 진행하는 프로그램을 쉽게 기술한다.

  • 파이썬 스레드를 사용해서 여러 시스템 콜을 병렬할 수 있고, 이를 활용하면 블로킹 I/O와 계산을 동시에 수행할 수 있다.

profile
성장을 도울 아카이빙 블로그

0개의 댓글