나는 이렇게 학습한다/Language

파이썬 멀티 프로세싱, GIL

daco2020 2022. 1. 24. 23:17
반응형

GIL(global interpreter lock)

  • 한 순간에 1개의 스레드만 유지하는 락
  • GIL은 본질적으로 한 스레드가 다른 스레드를 차단해서 제어를 얻는 것을 막음
    멀티스레딩의 위험으로부터 보호(하나의 메모리를 공유하므로 오류로 인해 전체가 다운될 수 있음)
  • 그러므로 파이썬은 스레드로 병렬성 연산을 수행하지 못함
    • 파이썬 멀티 스레딩은 동시성을 사용하여 io bound 코드(네트워크)에서 유용하게 사용할 수 있음
    • 하지만 동시성은 cpu bound 코드(연산만 있는)에서 이점이 없음

 

해결책

멀티 프로세싱으로 병렬성을 구현하여 효율을 높일 수 있음.
단, 프로세스끼리 메모리를 공유하지 않기 때문에 직렬화 역직렬화에 소요되는 비용이 큼

 

비교 실험

멀티스레딩 22초 <> 멀티프로세싱 13초
(하단 비교 코드 첨부)

 

결론

멀티스레딩은 cpu_bound 에서 의미가 없으며 오히려 스레드 생성 연산이 추가되므로 더 느려진다.




 

멀티 스레딩을 사용한 경우 ( 22 초 )

일반 계산과 차이가 없거나 더 느리다

from concurrent.futures import ThreadPoolExecutor
import time
import os
import threading

nums = [30] * 100


def cpu_bound_func(num):
    print(f"{os.getpid()} process | {threading.get_ident()} thread")
    numbers = range(1, num)
    total = 1
    for i in numbers:
        for j in numbers:
            for k in numbers:
                total *= i * j * k
    return total


def main():
    executor = ThreadPoolExecutor(max_workers=10)
    result = list(executor.map(cpu_bound_func, nums))
    print(result)


if __name__ == "__main__":
    start = time.time()
    main()
    end = time.time()
    print(end - start)

 

 

멀티 프로세싱을 사용한 경우 ( 13 초 )

import time
import os
import threading
from concurrent.futures import ProcessPoolExecutor

nums = [30] * 100


def cpu_bound_func(num):
    print(f"{os.getpid()} process | {threading.get_ident()} thread")
    numbers = range(1, num)
    total = 1
    for i in numbers:
        for j in numbers:
            for k in numbers:
                total *= i * j * k
    return total


def main():
    executor = ProcessPoolExecutor(max_workers=10)
    result = list(executor.map(cpu_bound_func, nums))
    print(result)


if __name__ == "__main__":
    start = time.time()
    main()
    end = time.time()
    print(end - start)

 

 

반응형