🐍 Python GIL

DoonDoonΒ·2019λ…„ 3μ›” 17일
10
post-thumbnail

A thumbnail was created from banner.godori.dev

πŸ“š Contents


  1. 🐢 GIL?
  2. πŸ™…β€β™‚οΈ 그럼 μ“°λ ˆλ“œλ₯Ό 쓰지 λ§κΉŒμš”?
  3. πŸ™„ CPU Bound Task / I/O Bound Task
  4. 🎀 Examples
  5. 마치며

🐢 GIL?


νŒŒμ΄μ¬μ„ κ³΅λΆ€ν•˜μ‹ λŠ” 뢄듀은 GIL μ΄λΌλŠ” 것을 ν•œ λ²ˆμ”© λ‹€ λ“€μ–΄ 보셨을거라 μƒκ°ν•©λ‹ˆλ‹€
Global Interpreter Lock 의 μ•½μžμΈ 이것은
파이썬 인터프리터가 ν•œ μ“°λ ˆλ“œλ§Œ ν•˜λ‚˜μ˜ λ°”μ΄νŠΈμ½”λ“œλ§Œ μ‹€ν–‰ν•  수 있게 ν•˜λŠ” 것이죠

GIL은 ν•˜λ‚˜μ˜ μ“°λ ˆλ“œμ—κ²Œ λͺ¨λ“  μžμ›μ˜ 점유λ₯Ό ν—ˆλ½ν•©λ‹ˆλ‹€
λ°˜λŒ€λ‘œ μ–˜κΈ°ν•˜λ©΄, λ‹€λ₯Έ μ“°λ ˆλ“œλŠ” μžμ›μ„ Acquire ν•˜κΈ° μ „μ—λŠ” μ•„μ˜ˆ μ‹€ν–‰μ‘°μ°¨ 될 수 μ—†λ‹€λŠ” 뜻이죠

Why GIL?


파이썬의 λͺ¨λ“  것은 κ°μ²΄μž…λ‹ˆλ‹€. 그리고 CPython μ—μ„œ 각각의 객체듀은
ν•˜λ‚˜μ˜ C ꡬ쑰체와 λŒ€μ‘λ©λ‹ˆλ‹€. κ²°κ΅­ μ—„μ²­λ‚˜κ²Œ 객체가 많이 μ‘΄μž¬ν•œλ‹€λŠ” 것이죠

각 파이썬 κ°μ²΄λŠ” GC λ₯Ό μœ„ν•΄ reference count (μ°Έμ‘° 횟수) λ₯Ό 가지고 μžˆμŠ΅λ‹ˆλ‹€
reference count λŠ” 병렬 ν”„λ‘œκ·Έλž˜λ°μ—μ„œ ν”νžˆ μ ‘ν•  수 μžˆλŠ”
곡유 μžμ› μ ‘κ·Όμ‹œμ˜ rade condition 문제λ₯Ό κ²ͺ을 수 μžˆμŠ΅λ‹ˆλ‹€

typedef struct _object { 
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type; 
} PyObject;

(μœ„ PyObject μ—μ„œ ob_refcnt κ°€ μ°Έμ‘° 횟수λ₯Ό μΉ΄μš΄νŒ… ν•˜λŠ” κ°’ μž…λ‹ˆλ‹€)

κ·Έλ ‡λ‹€κ³ , 각 객체가 ob_refcnt 에 μ ‘κ·Όν•  λ•Œ λ§ˆλ‹€ κ°œλ³„ mutex λ₯Ό ν™œμš©ν•΄μ„œ
thread-safe ν•˜λ„λ‘ λ§Œλ“€κΈ°μ—λŠ” μ„±λŠ₯μƒμ˜ 손해와, deadlock의 μœ„ν—˜ 등이 μ‘΄μž¬ν•©λ‹ˆλ‹€

κ·Έλž˜μ„œ κ·Έλƒ₯ μž κΆœμŠ΅λ‹ˆλ‹€. 인터프리터λ₯Ό μž κΆœμŠ΅λ‹ˆλ‹€ πŸ”’
λ§Žμ€ κΈ€μ—μ„œ κ·Έ λ‹Ήμ‹œμ˜ μ»΄ν“¨νŒ… ν™˜κ²½μ„ μƒκ°ν•˜λ©΄, λ‹Ήμ—°ν•œ 선택이라고 ν•©λ‹ˆλ‹€

μ•„λ¬΄νŠΌ λ‹€μ‹œ λŒμ•„μ˜€λ©΄

병렬 ν”„λ‘œκ·Έλž˜λ°μ—μ„œ μ–˜κΈ°ν•˜λŠ” μž‘μ—…κ°„μ˜ 동기화 정도에 λ”°λ₯Έ λΆ„λ₯˜λ₯Ό 크게

  1. coarse-grained lock
  2. fine-grained lock

으둜 λΆ„λ₯˜ν•˜λŠ”데, μ΄λŠ” ν”„λ‘œμ„Έμ„œμ˜ ν•˜μœ„ μž‘μ—…κ°„μ˜ ν†΅μ‹ μ˜ 정도λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€.
μž‘μ—…κ°„μ˜ 동기화와 톡신이 잦으면, fine-grained lock 으둜 λ‚˜λ‰˜μ—ˆλ‹€κ³  ν•˜λ©°
그렇지 μ•Šλ‹€λ©΄, coarse-grained lock 으둜 λ‚˜λ‰˜μ—ˆλ‹€κ³  ν•©λ‹ˆλ‹€

파이썬의 GIL은 ν•˜λ‚˜μ˜ μ“°λ ˆλ“œκ°€ μΈν„°ν”„λ¦¬ν„°μ˜ λͺ¨λ“  것을 κ°€μ Έκ°‘λ‹ˆλ‹€
ꡉμž₯히 ν„°ν”„ν•˜κ²Œ Lock 이 λ°œμƒν•˜κΈ° λ•Œλ¬Έμ—
사싀상 coarse-grained λ˜λŠ” embarrassingly parallel(처치 κ³€λž€ 병렬) 의 ν˜•νƒœλΌ ν•  수 μžˆμŠ΅λ‹ˆλ‹€

GIL 둜 인해 λ‹€λ₯Έ 언어와 같이 λ©€ν‹°μ“°λ ˆλ”© ν”„λ‘œκ·Έλž˜λ°μ΄ μ œν•œλ˜μ–΄
λ­”κ°€ μ„±λŠ₯μƒμ˜ μž‡μ μ„ μ•—μ•„κ°„λ‹€κ³  생각이 λ“œμ‹œκ² μ§€λ§Œ
μ‹€μ œλ‘œ Single Thread ν™˜κ²½μ—μ„œμ˜ μ„±λŠ₯은 μ’‹μŠ΅λ‹ˆλ‹€

그리고 GIL에 λŒ€ν•œ κ°œμ„ μ•ˆμ— λŒ€ν•˜μ„œ 귀도 반 λ‘œμΈμ€ μ΄λ ‡κ²Œ μ–˜κΈ°ν–ˆμŠ΅λ‹ˆλ‹€

But I also don't expect it to go away
until someone other than me goes through the effort of removing it,
and showing that its removal doesn't slow down single-threaded Python code.

단일 μ“°λ ˆλ“œ μ„±λŠ₯을 μ €ν•˜μ‹œν‚€μ§€ μ•ŠμœΌλ©΄μ„œ ν•œ 번 κ°œμ„ ν•΄μ„œ 와봐

πŸ™…β€β™‚οΈ 그럼 μ“°λ ˆλ“œλ₯Ό 쓰지 λ§κΉŒμš”?


파이썬의 μ“°λ ˆλ“œκ°€ 인터프리터λ₯Ό μ μœ ν•˜λ‹€κ°€, I/O Bound μž‘μ—… (λ„€νŠΈμ›Œν¬, 파일I/O λ“±) 이 λ°œμƒν•˜λ©΄
인터프리터 점유λ₯Ό release λ₯Ό ν•˜κ²Œ λ©λ‹ˆλ‹€

λ§Œμ•½μ—, HTTP Request λ₯Ό μ“°λ ˆλ“œλ₯Ό μ‚¬μš©ν•΄μ„œ μž‘μ—…ν•˜κ²Œ λœλ‹€λ©΄
μ„±λŠ₯ κ°œμ„ μ΄ μžˆμ„κ²ƒ κ°™λ‹€λŠ” 생각이 λ“­λ‹ˆλ‹€

(κ·Έ μžμ„Έν•œ μ½”λ“œλŠ” μ•„λž˜μ—μ„œ...)

πŸ™„ CPU Bound Task / I/O Bound Task


CPU Bound Task 의 κ²½μš°μ—λŠ” νŒŒμ΄μ¬μ—μ„œ λ©€ν‹°μŠ€λ ˆλ“œλ‘œ κ΅¬ν˜„ν•˜λŠ” κ²½μš°μ— κ°™κ±°λ‚˜ 더 λŠλ €μ§‘λ‹ˆλ‹€
더 λŠλ €μ§€λŠ” μ΄μœ λŠ” μ“°λ ˆλ“œκ°„μ˜ Lock 을 Acquire / Release ν•˜λŠ” κ³Όμ •μ—μ„œ
Context Switching 에 λŒ€ν•œ μ˜€λ²„ν—€λ“œκ°€ 원인 일것 κ°™μŠ΅λ‹ˆλ‹€

CPU Bound Task λŠ” CPU κ°€ μ²˜λ¦¬ν•˜λ©΄ 더 λΉ λ₯Έ μž‘μ—…λ“€μ„ μ˜λ―Έν•©λ‹ˆλ‹€... ν—€ν—€
주둜 μ—°μ‚°μž‘μ—…μ΄ 될 것 κ°™κ³ , 사싀 I/O Bound Task λ₯Ό μ œμ™Έν•œ 것은 λͺ¨λ‘ CPU μ—μ„œ μ²˜λ¦¬ν•˜κ² μ£ 

I/O Bound Task λŠ” burst λ˜λŠ” μ‹œκ°„μ˜ λŒ€λΆ€λΆ„μ„ I/O 에 ν• μ• ν•˜λŠ” μž‘μ—…μ„ μ˜λ―Έν•©λ‹ˆλ‹€
이λ₯Όν…Œλ©΄, μ›Ήμ—μ„œ μ–΄λ–€ 데이터λ₯Ό λ°›μ•„μ˜€κ±°λ‚˜, νŒŒμΌμ„ 읽고 μ“°λŠ” λ“±μ˜ μž‘μ—…μ€ CPU 보닀
파일 μ‹œμŠ€ν…œκ³Ό λ„€νŠΈμ›Œν¬ λ“± ν•˜μœ„ μ»΄ν¬λ„ŒνŠΈκ°€ κ·Έ μž‘μ—…μ„ 끝내기λ₯Ό κΈ°λ‹€λ¦¬λŠ” μ‹œκ°„μ΄ κΈΈλ‹€λŠ” 것이죠

예λ₯Ό λ“€λ©΄, Network μž‘μ—…, Database I/O, Disk I/O 등이 μžˆμŠ΅λ‹ˆλ‹€
이런 μž‘μ—…μ„ 파이썬 λ©€ν‹° μ“°λ ˆλ“œλ‘œ κ΅¬ν˜„ ν•œλ‹€λ©΄ 쑰금 더 μ„±λŠ₯ κ°œμ„ μ΄ μžˆμ„ 것 κ°™μ•„μš”

🎀 Examples


λ™κΈ°μ μœΌλ‘œ (일반적인 ν˜•νƒœλ‘œ) κ΅¬κΈ€μ˜ μ—°κ΄€ 검색어 10개λ₯Ό λ°›μ•„μ˜€λŠ” μ½”λ“œλ₯Ό ν…ŒμŠ€νŠΈ ν–ˆμŠ΅λ‹ˆλ‹€
λ„€νŠΈμ›Œν¬ ν™˜κ²½μ— 따라 λ‹€λ₯΄κ² μ§€λ§Œ, λŒ€λž΅ 3~4초 정도가 μ†Œμš” λ©λ‹ˆλ‹€

urls = [
	'https://www.google.com/complete/search?q=seungri&cp=1&client=psy-ab&xssi=t&gs_ri=gws-wiz&hl=ko&authuser=0&pq=google&psi=ty6OXKGCIMnFmAXDraP4Bw.1552821944139&ei=ty6OXKGCIMnFmAXDraP4Bw',
    # μ•„λž˜ μΆ”κ°€ 9개의 urlλ“€...
]

@timer # timer λ°μ½”λ ˆμ΄ν„°λŠ” μž‘μ—…μ— μ†Œμš”λ˜λŠ” μ‹œκ°„μ„ ν”„λ¦°νŠΈ ν•˜λŠ” λ°μ½”λ ˆμ΄ν„° μž…λ‹ˆλ‹€
def fetch_single_thread(urls):
    for url in urls:
        response = fetch(url)
        print(response)

if __name__ == '__main__':
    fetch_single_thread(urls) # Time Elasped 3.02 seconds

μ•„λž˜μ™€ 같이 λ©€ν‹°μ“°λ ˆλ”©μœΌλ‘œ κ΅¬ν˜„ν•œλ‹€λ©΄, μ„±λŠ₯μƒμœΌλ‘œ κ°œμ„ μ΄ 될 것이라 μƒκ°ν•©λ‹ˆλ‹€
κ²°κ³Όκ°€ 잘 λ“€μ–΄μ™”λŠ”μ§€ ν™•μΈν•˜κΈ° μœ„ν•΄ Queue λ₯Ό λ§Œλ“€μ–΄, κ²°κ³Όλ₯Ό Queue 에 λ„£μŠ΅λ‹ˆλ‹€
μ†Œμš”μ‹œκ°„μ€ μ•½ 0.33 초 μž…λ‹ˆλ‹€

@timer
def fetch_four_threads(urls):
    threads = []
    for url in urls:
        thread = threading.Thread(target=fetch, args=(url,))
        threads.append(thread)

    for t in threads:
        t.start()

    for t in threads:
        t.join()
        
if __name__ == '__main__':
    fetch_four_threads(urls)

마치며


"νŒŒμ΄μ¬μ— GIL 이 μžˆμœΌλ‹ˆ λ©€ν‹°ν”„λ‘œμ„ΈμŠ€λ§Œ 써야해" 처럼 μ•ˆμΌν•˜κ²Œ μƒκ°ν–ˆμ—ˆλŠ”λ°
이번 글을 κ³„κΈ°λ‘œ, μ–΄λŠ μ •λ„λŠ” λͺ…ν™•ν•˜κ²Œ Python 의 GIL 에 λŒ€ν•΄ 이해할 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€

글을 μ“°λŠ”λ° 데이빗 λΉ„μ¦λ¦¬λΌλŠ” 유λͺ…ν•œ λΆ„μ˜ μŠ¬λΌμ΄λ“œλ₯Ό 많이 μ°Έκ³  ν–ˆμŠ΅λ‹ˆλ‹€
(μ ˆλŒ€ μ € μŠ¬λΌμ΄λ“œμ˜ λ‚΄μš©μ„ λ‹€ μ΄ν•΄ν•œκ²Œ μ•„λ‹™λ‹ˆλ‹€... 40 νΌμ„ΌνŠΈλ„ λͺ» μ•Œμ•„ 듀은것 κ°™λ„€μš”)

CS κΈ°λ³Έ 지식이 λΆ€μ‘±ν•˜μ—¬, μ“°λ ˆλ“œμ™€ ν”„λ‘œμ„ΈμŠ€μ˜ κ°œλ…μ„ 잘 λͺ¨λ₯΄λ‹ˆ
μ—¬λŸ¬κ°€μ§€ 쒋은 글을 μ΄ν•΄ν•˜λŠ”λ° μ‹œκ°„μ΄ 많이 κ±Έλ ΈμŠ΅λ‹ˆλ‹€

ν˜Ήμ‹œ κ΄€λ ¨ν•œ 지식을 μŠ΅λ“ν•˜λŠ”λ° 도움이 ν•„μš”ν•˜μ‹œλ‹€λ©΄
μ œκ°€ κ³΅λΆ€ν•˜κ³  μžˆλŠ” 유튜브 μž¬μƒλͺ©λ‘ 이 도움이 λœλ‹€λ©΄ μ’‹κ² λ„€μš”!

References


Davie Beazley 의 2010 PyCon Slide - Understanding the Python GIL
파이썬의 μŠ€λ ˆλ“œ μ‚¬μš©λ²•
Parallel HTTP Requests in Python
슭의 개발 λΈ”λ‘œκ·Έ - Global Interpreter Lockμ΄λž€?
μ˜μ–΄κ°€ 싫은 개발자 - Pythonμ—μ„œ threadλ₯Ό μ‚¬μš©ν•˜μ§€ λ§ˆμ„Έμš”?
python의 GIL(Global Interpreter Lock)
Understanding Threads in Python
Real Python - What is the Python Global Interpreter Lock (GIL)?
κ°œλ°œμƒˆλ°œλ‘œκ·Έ - μ™œ Pythonμ—λŠ” GIL이 μžˆλŠ”κ°€
파이썬 GIL κΉŠμˆ™νžˆ! (上)
β†’ 2006λ…„ 이면 μ €λŠ” 개발의 κ°œλ„ λͺ¨λ₯΄λ˜ λ•Œ 같은데 μ„ λ°°λ‹˜λ“€μ˜ 내곡 정말 λŒ€λ‹¨ ν•˜μ‹­λ‹ˆλ‹€...

profile
Espresso Cream Cat DD

2개의 λŒ“κΈ€

쒋은 κΈ€ κ°μ‚¬ν•©λ‹ˆλ‹€! celery 와 threading 을 κ³΅λΆ€ν•˜λ˜ 와쀑에 κΆκΈˆν•œ 점이 많이 ν•΄κ²°λ˜μ—ˆλ„€μš”, μ•žμœΌλ‘œλ„ μ°Έκ³ ν•˜λŸ¬ μ’…μ’… λ“€λ₯΄κ² μŠ΅λ‹ˆλ‹€.

1개의 λ‹΅κΈ€