728x90
반응형

저번에 학습한 이론대로라면 Thread 2개를 만들어서 실행하면 아래와 같은 그림으로 동작된다고 생각할 수 밖에 없다.

(참조 : https://yscho03.tistory.com/293)

하지만 파이썬은 아래와 같이 동작하지 않는다.

파이썬에는 GIL(Global Interpreter Lock) 이 있기때문에  스레드에서 한 번에 하나의 스레드만 Python 바이트코드를 실행한다.

 

GIL(Global Interpreter Lock)

GIL(Global Interpreter Lock)은 CPython 인터프리터(파이썬의 표준 구현체)에서 사용되는 개념이다. GIL은 파이썬 스레드에서 한 번에 하나의 스레드만 Python 바이트코드를 실행하도록 제한하는 메커니즘이다. GIL은 파이썬이 C 언어로 작성된 확장 모듈을 사용할 때 스레드 안전성을 보장하기 위해 도입되었다.

GIL은 파이썬 객체의 참조 카운팅(refcounting) 메커니즘을 보호하고 파이썬 스레드 간의 동기화 문제를 해결하기 위해 사용됩니다. 이로 인해 CPython에서는 동시에 여러 개의 스레드를 사용해도 여전히 하나의 스레드만이 실행될 수 있다. GIL로 인해 CPU 바운드 작업에서는 파이썬 스레드의 병렬성이 제한되지만, I/O 바운드 작업(네트워크 요청, 파일 입출력 등)에서는 여전히 이점을 얻을 수 있다.

 

 

출처 : https://www.datacamp.com/tutorial/python-global-interpreter-lock

 

 

무슨말인지 예제를 하나 작성해보자

아래 코드에서 increment 함수는 전역 변수 counter를 1000000번 증가시키는 작업을 수행한다. t1과 t2는 두 개의 스레드로 increment 함수를 동시에 호출한다. 하지만 GIL로 인해 하나의 스레드만이 실행되고, 나머지 스레드는 대기하게 된다. 따라서 출력 결과에서 counter 값은 예상보다 작게된다.

import threading

# 전역 변수
counter = 0

# 쓰레드 동작 함수
def increment():
    global counter
    for _ in range(1000000):
        counter += 1

# 메인 스레드
if __name__ == '__main__':
    # 두 개의 스레드 생성
    t1 = threading.Thread(target=increment)
    t2 = threading.Thread(target=increment)
    
    # 스레드 시작
    t1.start()
    t2.start()
    
    # 스레드 종료 대기
    t1.join()
    t2.join()
    
    # 결과 출력
    print("Counter:", counter)
    # Counter: 1087951

실제로 출력된 값은 Counter: 1087951 이다. GIL이라는 개념을 몰랐다면 2000000이라고 예상했을 것이다.

 

 

GIL을 피해서 병렬 처리를 해보자

이 GIL 성능 제약의 메커니즘을 피하는 방법을 multiprocessing을 사용하는 방법이 있다.

아래 코드에서 multiprocessing.Value를 사용하여 counter 변수를 프로세스 간에 공유 가능한 형태로 만들었고 increment 함수에서는 counter 변수를 증가시키는 작업을 수행한다, with counter.get_lock():를 사용하여 프로세스 간의 동기화를 보장한다. 이렇게 함으로써 GIL을 우회하고 병렬 처리를 할 수 있다.

import multiprocessing

def increment(counter):
    for i in range(1000000):
        with counter.get_lock():
            counter.value += 1

if __name__ == '__main__':
    counter = multiprocessing.Value('i', 0)

    process1 = multiprocessing.Process(target=increment, args=(counter,))
    process2 = multiprocessing.Process(target=increment, args=(counter,))

    process1.run()
    process2.run()

    print("Counter:", counter.value)
728x90
반응형