프로그래밍 언어/Python

두 코드의 차이점을 설명하시요( feat. Python,쓰레드, GIL, 병렬 I/O)

JMDev 2023. 9. 15. 17:57
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'Took {delta:.3f} seconds')
# Took 0.526 seconds
import select
import socket

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

start = time.time()

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

# Example 9
def compute_helicopter_location(index):
    pass

for i in range(5):
    compute_helicopter_location(i)

for thread in threads:
    thread.join()

end = time.time()
delta = end - start
print(f'Took {delta:.3f} seconds')
# Took 0.103 seconds

여러분들은 위의 두 코드를 보고 속도의 차이점을 명확히 설명할 수 있나요?

만약 단순히 쓰레드 라는 특성만이라고 한다면 아직 완벽한 대답이 될 수 없을 것 입니다.

 

쓰레드 때문이라고 생각하시는 분들은 GIL(Global Interpreter Lock)를 덧붙여 설명해주실 수 있나요?

 

만일 GIL를 설명하였다면 GIL 특성으로 멀티쓰레드 중 단 하나의 쓰레드만 활성시킬 수 있음을 알고 있다는 것인데,

그렇다면 어떻게 아래의 코드는 위의 코드보다 더 빠른 실행결과를 보여줄 수 있었던 것일까요?

GIL에 의하여 쓰레드는 단 하나의 쓰레드로 독점이 될것이고 그 전제가 맞다면 위와 동일한 속도를 보여줘야 할텐데요.

 

그 이유를 이해하기 위해선 slow_systemcall() 의 내부코드가 시스템 콜에 의한 I/O작업이 되고 있음을 알야아 합니다.

 

파이썬 스레드는 여러 시스템 콜을 병렬로 처리할 수 있는 기능을 가지고 있습니다.

그럼으로 I/O 작업들을 병행하여 처리할 수 있다는 뜻이 되겠죠. 좀 더 구체적으로 쓰레드가 slow_systemcall() 를 실행합니다.

해당 내부기능에 I/O 작업을 발견하고, GIL를 해제합니다.

그리고 다음 쓰레드를 실행하고, 다시 해제하는 것을 반복하여 병렬로 실행히 가능하게 된 것이죠.