Python GIL, Global interpreter Lock
๐ก Intro
์ค๋์ Python์ ๊ฐ์ฅ ํฐ ํน์ง์ค ํ๋์ธ GIL(Global interpreter Lock)์ ๋ํด ์์๋ณด๋ ค๊ณ ํฉ๋๋ค. Python์ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์๋ผ๋ฉด ๋๊ตฌ๋ ๋ค ํ๋ฒ์ฏค์ ๋ค์ด๋ดค์ ๊ฒ์ด๊ณ , ์ ๋ํ GIL์ ๋ํด ๊ณต๋ถํด ๋ณธ์ ์ด ์์ง๋ง ๋ค์ ํ๋ฒ ์ ๋ฆฌํด ๋ณด๋ ค๊ณ ํฉ๋๋ค.
๐ Python ์ธํฐํ๋ฆฌํฐ๋?
์ฐ์ GIL์ด๋ Global Interpreter Lock์ ์ฝ์๋ก ํ์ด์ฌ ์ธํฐํ๋ฆฌํฐ๊ฐ ํ ์ฐ๋ ๋๋ง์ด ํ๋์ ๋ฐ์ดํธ์ฝ๋๋ฅผ ์คํ ์ํฌ ์ ์๋๋ก ํด์ฃผ๋ Lock์ ๋๋ค. ์ฆ, Python ์ธํฐํ๋ฆฌํฐ๋ ํ ๋ฒ์ ํ ์ฐ๋ ๋๋ง ์คํ๋ ์ ์๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
๊ทธ๋ ๋ค๋ฉด ํ์ด์ฌ ์ธํฐํ๋ฆฌํฐ๋ ๋ฌด์์ผ๊น์? Python ์ธํฐํ๋ฆฌํฐ๋, Python์ผ๋ก ์์ฑ๋ ์ฝ๋๋ฅผ ํ ์ค์ฉ ์ฝ์ผ๋ฉด์ ์คํํ๋ ํ๋ก๊ทธ๋จ์ ๋ป ํฉ๋๋ค. Python ์ธํฐํ๋ฆฌํฐ์ ๋ํ ์์ธํ ๋ด์ฉ์ ํ์ด์ฌ์ ์ธํฐํ๋ฆฌํฐ์ธ์ด์ ๋๊น? ๋ธ๋ก๊ทธ์์ ์กฐ๊ธ ๋ ์ฌ๋๊น๊ฒ ์๊ฐํด๋ณด์ค ์ ์์ต๋๋ค.
๐ GIL(Global Interpreter Lock)
GIL (Global Interpreter Lock)์ ๐Python ์ํค์์๋ ๋ค์๊ณผ ๊ฐ์ด ์ ์ํ๊ณ ์์ต๋๋ค.
In CPython, the global interpreter lock, or GIL, is a mutex that protects access to Python objects, preventing multiple threads from executing Python bytecodes at once. The GIL prevents race conditions and ensures thread safety. A nice explanation of ๐how the Python GIL helps in these areas can be found here. In short, this mutex is necessary mainly because CPythonโs memory management is not thread-safe.
ํด์์ ํด๋ณด๋ฉด, Python์ ๊ฐ์ฒด๋ค์ ๋ํ ์ ๊ทผ์ ๋ณดํธํ๋ ์ผ์ข ์ Mutex๋ก์ ์ฌ๋ฌ ๊ฐ์ ์ฐ๋ ๋๊ฐ ํ์ด์ฌ ์ฝ๋๋ฅผ ๋์์ ์คํํ์ง ๋ชปํ๋๋ก ํ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์ฆ, ํ ํ๋ก์ธ์ค ๋ด์์ Python ์ธํฐํ๋ฆฌํฐ๋ ํ ์์ ์ ํ๋์ ์ฐ๋ ๋์ ์ํด์๋ง ์คํ๋ ์ ์์ต๋๋ค. ๋ฉํฐ ์ฐ๋ ๋ฉ์ด ๋ถ๊ฐ๋ฅํ๋ค๋ ๊ฒ์ด ์๋๋, ์๋ ๋ฉํฐ ์ฝ์ด๋ผ๋ฉด ๋ฉํฐ ์ฐ๋ ๋ฉ ์์ ์ฌ๋ฌ ๊ฐ์ ์ฐ๋ ๋๊ฐ ์ฌ๋ฌ ์ฝ์ด ์์์ ๋ณ๋ ฌ์คํ๋ ์ ์๋๋ฐ, Python์์๋ ๊ทธ๋ฌํ ๋ณ๋ ฌ ์คํ์ด ๋ถ๊ฐ๋ฅํ๋ค๋ ๊ฒ์ ๋๋ค.
๊ฐ๋จํ๊ฒ ์ค๋ช ์ ํด๋ณด์๋ฉด, 3๊ฐ์ ์ฐ๋ ๋๋ฅผ ํตํด ์์ ์ ํ๋ค๊ณ ๊ฐ์ ํ์ ๋ ํ๋์ ์ฐ๋ ๋์ ๋ชจ๋ ์์์ ํ๋ฝํ๊ณ ๊ทธ ํ์๋ Lock์ ๊ฑธ์ด ๋ค๋ฅธ ์ค๋ ๋๋ ์คํํ ์ ์๊ฒ ๋ง์๋ฒ๋ฆผ์ผ๋ก์จ, ๊ฐ๊ฐ์ ์ฐ๋ ๋๋ GIL์ ์ป๊ณ ๋์ํ์ง๋ง ์ด ๋ ๋ค๋ฅธ ์ฐ๋ ๋๋ ๋ชจ๋ ๋์์ ๋ฉ์ถ๊ฒ ๋ฉ๋๋ค. ์ด๋ฅผ ๊ทธ๋ฆผ์ผ๋ก ๋ํ๋ด๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
๐ ๊ทธ๋ผ GIL์ด ์ ์๊ธด๊ฑธ๊น?
Python์์ ๋ชจ๋ ๊ฒ์ ๊ฐ์ฒด(Object)์ ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๊ฐ ๊ฐ์ฒด๋ Reference Count๋ฅผ ์ ์ฅํ๊ธฐ ์ํ ํ๋๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค.
Reference Count๋ ๊ทธ ๊ฐ์ฒด๋ฅผ ๊ฐ๋ฆฌํค๋ Reference๊ฐ ๋ช ๊ฐ ์กด์ฌํ๋์ง๋ฅผ ๋ํ๋ด๋ ๊ฒ์ผ๋ก, Python์์์ GC(Garbage Collection)๋ ์ด๋ฌํ Reference Count๊ฐ 0์ด ๋๋ฉด ํด๋น ๊ฐ์ฒด๋ฅผ ๋ฉ๋ชจ๋ฆฌ์์ ์ญ์ ์ํค๋ ๋ฉ์ปค๋์ฆ์ผ๋ก ๋์ํ๊ณ ์์ต๋๋ค.
๋ฐ๋ผ์ ํ์ด์ฌ์ ๋ชจ๋ ๊ฐ์ฒด๋ Reference count, ์ฆ ํด๋น ๋ณ์๊ฐ ์ฐธ์กฐ๋ ์๋ฅผ ์ ์ฅํ๊ณ ์์ต๋๋ค. ์ฌ๊ธฐ์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ฒ ๋๋๋ฐ, ๋ฉํฐ ์ฐ๋ ๋์ธ ๊ฒฝ์ฐ ์ฌ๋ฌ ์ฐ๋ ๋๊ฐ ํ๋์ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ค๋ฉด Reference count๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํด์ ๋ชจ๋ ๊ฐ์ฒด์ ๋ํ lock์ด ํ์ํ ๊ฒ์ ๋๋ค.
์ด๋ฌํ ๋นํจ์จ์ ๋ง๊ธฐ ์ํด์ Python์์ GIL์ ์ฌ์ฉํ๊ฒ ๋์์ต๋๋ค. ๊ทธ ๋น์ GIL์ ์ ํํด์ผ ํ๋ ์ด์ ๋ ๐What Is the Python Global Interpreter Lock (GIL)? ์์ ์ฐพ์ ์ ์์์ต๋๋ค.
Python has been around since the days when operating systems did not have a concept of threads. Python was designed to be easy-to-use in order to make development quicker and more and more developers started using it. A lot of extensions were being written for the existing C libraries whose features were needed in Python. To prevent inconsistent changes, these C extensions required a thread-safe memory management which the GIL provided. The GIL is simple to implement and was easily added to Python. It provides a performance increase to single-threaded programs as only one lock needs to be managed. C libraries that were not thread-safe became easier to integrate. And these C extensions became one of the reasons why Python was readily adopted by different communities. As you can see, the GIL was a pragmatic solution to a difficult problem that the CPython developers faced early on in Pythonโs life.
๐ ๊ทธ๋ ๋ค๋ฉด Python ๋ฉํฐ์ฐ๋ ๋ฉ์ ๋ฌด์กฐ๊ฑด ๋๋ฆด๊น?
CPU ์ฐ์ฐ์ ๋น์ค์ด ํฐ ์์ ์ ํ ๋, Context Switching์ผ๋ก ์ธํด ๊ดํ Cost๋ง ์ก์๋จน๊ธฐ ๋๋ฌธ์ ๋ฉํฐ ์ฐ๋ ๋ฉ์ ์คํ๋ ค ์ฑ๋ฅ์ ๋จ์ด๋จ๋ฆฌ๊ฒ ๋ฉ๋๋ค. ํ์ง๋ง I/O, Sleep ๋ฑ์ ์ธ๋ถ ์ฐ์ฐ์ ํ๋๋ผ CPU๊ฐ ์๋ฌด๊ฒ๋ ํ์ง ์๊ณ ๊ธฐ๋ค๋ฆฌ๊ธฐ๋ง ํ ๋๋ ๋ค๋ฅธ ์ฐ๋ ๋๋ก Context Switching์ ํ๊ฒ ๋ฉ๋๋ค.
๊ทธ๋ฌํ ์ด์ ๋ก CPU ์ฐ์ฐ์ ๋น์ค์ด ์ ์ I/O, Sleep ๋ฑ์ ์ธ๋ถ ์ฐ์ฐ ๊ฐ์ด ๋น์ค์ด ํฐ ์์ ์ ํ ๋๋ ๋ฉํฐ ์ฐ๋ ๋ฉ์ด ๊ต์ฅํ ์ข์ ์ฑ๋ฅ์ ๋ณด์ฌ์ฃผ๊ฒ ๋ฉ๋๋ค. ์ฆ, Python ๋ฉํฐ์ฐ๋ ๋ฉ์ ๋ฌด์กฐ๊ฑด ๋๋ฆฌ๋ค๋ผ๋ ๋ง์ ๋ง๋ ๋ง์ด ์๋๋๋ค. ๋ค์ ์์๋ฅผ ํตํด ์ค๋ช ์ด ๊ฐ๋ฅํฉ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import random
import threading
import time
def working():
time.sleep(1)
max([random.random() for i in range(10000000)])
time.sleep(1)
max([random.random() for i in range(10000000)])
time.sleep(1)
# Single Thread
s_time = time.time()
working()
working()
e_time = time.time()
print(f'{e_time - s_time:.5f}')
# Multi Thread
s_time = time.time()
threads = []
for i in range(2):
threads.append(threading.Thread(target=working))
threads[-1].start()
for t in threads:
t.join()
e_time = time.time()
print(f'{e_time - s_time:.5f}')
์๋์ ๊ฐ์ด ํ์ฐํ ์ฐจ์ด๋ฅผ ๋ณผ ์ ์์ต๋๋ค.
1
2
3
4
5
Single Thread -> 10.77272
Multi Thread -> 7.17564
Single Thread๋ sleep
์ผ๋ก ์ธํด ์๋ฌด ๊ฒ๋ ๋ชปํ๊ณ ๋์์ ๋๊ธฐํ๊ฒ ๋๊ณ , Multi Thread๋ sleep
์ผ๋ก ๋ฉ์ถ ์ํ์์ ๋ค๋ฅธ ์ค๋ ๋๋ก Context Switchingํ์ฌ Single Thread์ ํจ์จ์ ๊ฐ์ ํ๊ฒ ๋ฉ๋๋ค.
๋๋งบ์ Python์ผ๋ก ์์ ํ๋ ๊ฐ๋ฐ์์ ์ ์ฅ์ผ๋ก์จ ์ง๊ธ๊น์ง๋ GIL๋ก ์ธํ ๋ถํธํ์ ์ ๋๋ ์ ์์์ต๋๋ค. ํ์ง๋ง ๋ปํ์ง ์์ ๊ณ๊ธฐ๋ก ์ค๋๋ง์ ๋ค์ GIL(Global Interpreter Lock)์ ๊ณต๋ถํ๊ฒ ๋์๋๋ฐ, GIL์ ๋ํ ์ญ์ฌ์ Python์ GC์ ๋ํด ์ถฉ๋ถํ ์ดํดํ ์ ์์๋๊ฒ ๋ง์ผ๋ก๋ ๋งค์ฐ ์๋ฏธ์๋ ์๊ฐ์ด์๋ค๊ณ ์๊ฐํฉ๋๋ค.๐
[์ฐธ๊ณ ์๋ฃ]