π‘ λ³Έ λ¬Έμλ '[Python] Thread: GILκ³Ό Thread ꡬν/μ€ν, Event'μ λν΄ μ 리ν΄λμ κΈμ λλ€.
~~~μ 리νμμΌλ μ°Έκ³ νμκΈ° λ°λλλ€.
1. Thread λμ
νλ‘μΈμ€μ μ€λ λμμ μ€λ λκ° λ¬΄μμΈμ§ μμλ΄€μ΅λλ€. μ΄λ²μλ νμ΄μ¬μμ μ€λ λλ₯Ό ꡬννκ³ μ¬μ©νλ κΈ°λ³Έμ μΈ λ°©λ²κ³Ό μμμ 무결μ±κ³Ό λκΈ°νλ₯Ό μν μ²λ¦¬μ κ΄ν΄μ μμ보λλ‘ νκ² μ΅λλ€.
GIL(Global Interpreter Lock)
νμ΄μ¬ μ½λλ μΈν°ν리ν°κ° μ½λλ₯Ό λ²μνκ³ μ€νν¨μΌλ‘μ¨ μ€νλ©λλ€. μ¦, νμ΄μ¬ μ½λκ° μ€νλκΈ° μν΄μλ μΈν°ν리ν°λΌλ μμμ μμ νκ³ μμ΄μΌ νλ κ²μ λλ€. GILμ μΈν°ν리ν°κ° ν μμ μ νλμ μ€λ λλ§ μ€νν μ μλλ‘ ν΄μ£Όλ μΈν°νλ¦¬ν° μμ μ κ΄ν Lockμ λλ€.
Pythonμ μ€λ λ λμ
λ©ν°μ½μ΄ νκ²½μμ μ¬λ¬κ°μ μ€λ λλ₯Ό λμμν¨λ€λ©΄ κ°κ°μ μ€λ λλ€μ΄ λ³λ ¬μ μΌλ‘ λμνλ κ²μ μκ°νμ€κ²λλ€. νμ§λ§ νμ΄μ¬μμλ GILλ‘ μΈν΄ μΈν°ν리ν°κ° νλμ μ€λ λμ μν΄μλ§ μμ λ μ μμΌλ―λ‘ μλμ κ°μ λ°©μμΌλ‘ λμνκ² λ©λλ€.
κ°κ°μ μ€λ λκ° GILμ νλνμ¬ λμνκ³ GILμ λ€λ₯Έ μ€λ λμκ² μμ κΆμ λκΈ°λ©΄μ λμνκ² λ©λλ€. μ΄μ κ°μ λμ λ°©μμΌλ‘ μΈνμ¬ νμ΄μ¬μ κ²½μ° μ€λ λλ₯Ό λ³λ ¬μ μΌλ‘ μ²λ¦¬ν κ²½μ° μ€νλ € GILμ νλ/λ°λ©νλ κ³Όμ μ μ€λ²ν€λλ‘ μΈνμ¬ μ€νλ € μ€νμκ°μ΄ λλ €μ§κ² λλ λ¬Έμ κ° λ°μν μ μμ΅λλ€.
μ GILμ μ¬μ©νλ κ²μΈκ°?
νμ΄μ¬μμλ κ°μ²΄λ₯Ό reference countingμ ν΅ν΄ garbage collectionμ μ€νν©λλ€. κ° κ°μ²΄λ§λ€ μ°Έμ‘°λκ³ μλ νμλ₯Ό reference countμ μ μ₯νκ³ , μ΄ νμκ° 0μ΄ λλ©΄ garbage collectorκ° λ©λͺ¨λ¦¬λ₯Ό νμνλ κ²μ λλ€. λ°λΌμ μ΄ reference countλ κ°μ΄ λλ½νμ§κ² λλ κ²½μ° μ¬μ© μ€μΈ κ°μ²΄κ° μ¬λΌμ§κ±°λ, μ¬μ©λμ§ μλ κ°μ²΄κ° κ³μ λ©λͺ¨λ¦¬λ₯Ό μ°¨μ§νκ² λλ λ¬Έμ κ° λ°μν μ μκΈ° λλ¬Έμ, Lockμ ν΅ν΄ λκΈ°νλ₯Ό ν νμκ° μμ΅λλ€.
νμ§λ§ μ¬μ©λλ λͺ¨λ κ°μ²΄μ Lockμ μ¬μ©νμ¬ κ΄λ¦¬ν κ²½μ°, Lockμ κΈ°λ€λ¦¬κ³ Lockμ νλ/λ°ννλ μ€λ²ν€λμ Race condition λ± λ€μν λ¬Έμ κ° λ°μν μ μμ΅λλ€. λ°λΌμ Pythonμμλ μ μ΄μ μΈν°ν리ν°μ μ μμ μΌλ‘ Lockμ κ±Έμ΄ νλμ μ€λ λλ§ μ€νλ μ μλλ‘ νμ¬ λ€λ₯Έ μμμ λν λκΈ°ν λ¬Έμ λ₯Ό ν΄κ²°ν κ²μ λλ€.
GILμ CPU Boundμλ μ·¨μ½ν©λλ€. μμ λ§νλ―μ΄ μ¬λ¬ μ€λ λκ° μΈν°ν리ν°λ₯Ό μ£Όκ³ λ°μΌλ©° μ€ννκΈ° λλ¬Έμ μμμ νλ/λ°ννλ κ³Όμ μ μ€λ²ν€λλ‘ μΈνμ¬ μ±κΈ μ½μ΄ νκ²½μμλ³΄λ€ μ±λ₯μ΄ λμ± μμ’μμ§ μ μκΈ° λλ¬Έμ λλ€.
νμ§λ§ GILμΌλ‘ μΈν μ₯μ λ λΆλͺ ν μ‘΄μ¬ν©λλ€. I/O Boundμ κ²½μ° I/O μμ μμλ GILμ λ°ννμ¬ λ€λ₯Έ μ€λ λκ° λμν μ μλλ‘ νμ¬ μ±λ₯μ΄ κ°μ λ μ μμΌλ©°, μΈμΈνκ² Lockμ μ€κ³νμ§ μμλ λκΈ° λλ¬Έμ ꡬνμμ ν¨μ¬ κ°νΈν©λλ€.
2. Thread ꡬν
νμ΄μ¬μμ μ€λ λλ₯Ό ꡬννλ λ°©λ²μλ μ μμ€ λΌμ΄λΈλ¬λ¦¬, κ³ μμ€ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νλ λ°©λ²μ΄ μμ΅λλ€. μ μμ€μ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©νκ² λλ©΄ thread poolμ΄λ lockμ 컀μ€ν°λ§μ΄μ§νμ¬ μ¬μ©ν μ μκ³ , μ€λ λμ μμμ μΈ κΈ°λ₯λ€μ μνλλλ‘ λ³κ²½νμ¬ μ¬μ©ν μ μμ΅λλ€. νμ§λ§ ꡬνμ΄ μ΄λ ΅κ³ lockμ μ€κ³λ₯Ό ν΄μ£Όμ΄μΌνλ€λ λ¨μ μ΄ μμ΅λλ€. λ°λΌμ μΌλ°μ μΌλ‘ λ§μ΄ μ¬μ©νλ κ³ μμ€μ λΌμ΄λΈλ¬λ¦¬μΈ treading λͺ¨λμ μ¬μ©νμ¬ Threadλ₯Ό ꡬννλ λ°©λ²μ μμ보λλ‘ νκ² μ΅λλ€.
1) ν¨μλ₯Ό μ¬μ©ν Thread ꡬν
import threading
import time
def work(count):
time.sleep(0.1)
print("\nname : %s\nargument : %s\n"%(threading.currentThread().getName(), count))
# μ€λ λ μμ±μ μΈμλ‘ λ°μ name,count μΆλ ₯
def main():
for i in range(5):
t = threading.Thread(target=work, name="thread %i"%i, args=(i,))
t.start()
if __name__=="__main__":
main()
ν¨μλ₯Ό μ€λ λλ‘ λ§λ€μ΄ μ€ννλ λ°©λ²μ λλ€. threading λͺ¨λμ Thread ν΄λμ€λ₯Ό μ¬μ©νμ¬ thread κ°μ²΄λ₯Ό λ§λ€μ΄ start() λ©μλλ‘ μ€ννλ©΄ λ©λλ€. target μΈμλ‘λ μ€λ λλ‘ κ΅¬νν ν¨μλ₯Ό μΈμλ‘ μ λ¬νκ³ , name μΈμλ‘ ν΄λΉ μ€λ λμ μ΄λ¦μ ν λΉν μ μμ΅λλ€. λν argsλ₯Ό ν΅ν΄ μ€λ λλ‘ κ΅¬νν ν¨μμ νμν μΈμλ€μ μ λ¬ν μ μμ΅λλ€.
name : thread 1
argument : 1
name : thread 2
argument : 2
name : thread 0
argument : 0
name : thread 3
argument : 3
name : thread 4
argument : 4
μμ μ½λλ₯Ό μ€ννκ² λλ©΄ μμ²λΌ threadλ‘ κ΅¬νν λ©μλλ€μ΄ λΉμμ°¨μ μΌλ‘ μ€νλ κ²°κ³Όλ₯Ό νμΈν μ μμ΅λλ€.
2) ν΄λμ€λ₯Ό μ¬μ©ν Thread ꡬν
import threading
class Work(threading.Thread): # Thread μμ
def __init__(self, args, name=""): # μμ±μ ꡬν
threading.Thread.__init__(self, name=name)
self.args = args
def run(self): # run λ©μλ ꡬν
print("\nname : %s\nargument : %s\n"%(threading.currentThread().getName(), self.args[0]))
def main():
for i in range(5):
t = Work(name="thread % i"%i, args=(i,))
t.start()
if __name__=="__main__":
main()
ν΄λμ€λ₯Ό μ¬μ©νμ¬ threadλ₯Ό ꡬννλ λ°©λ²λ κ°λ¨ν©λλ€. treadingλͺ¨λμ Thread ν΄λμ€λ₯Ό μμ λ°μ λ€μ run λ©μλλ₯Ό ꡬννλ©΄ λ©λλ€. μμ±μλ₯Ό ꡬννλ κ²½μ°μλ Threadλ‘ κ΅¬νν ν΄λμ€ μμ±μ λ΄λΆμμ Thread ν΄λμ€μ μμ±μλ₯Ό νΈμΆν΄μΌ ν©λλ€.
μ€λ λ κ°μ²΄κ° start λ©μλλ₯Ό νΈμΆνλ©΄ λ΄λΆμ μΌλ‘ run λ©μλλ₯Ό νΈμΆνκ² λ©λλ€. λ°λΌμ μ€λ λμμ λμμν¬ λ‘μ§μ run λ©μλμ ꡬννλ©΄ λ©λλ€.
name : thread 0
argument : 0
name : thread 1
argument : 1
name : thread 4
argument : 4
name : thread 2
argument : 2
name : thread 3
argument : 3
3) Daemon Thread
μ€λ λλ₯Ό μ¬μ©νμ¬ λ°±κ·ΈλΌμ΄λμμ λμνμ¬ μ¬λ¬ μμ μ μννλ λ°λͺ¬μ ꡬννμ¬ μ¬μ©ν μ μμ΅λλ€. μ£Όμ μμ λ€μ main μ€λ λμμ μννκ³ , λ°±κ·ΈλΌμ΄λλ‘ μ€νν μμ μ μ€λ λλ‘ κ΅¬ννμ¬ λ°λͺ¬μΌλ‘ λμμν€λ κ²μ λλ€.
import threading
import time
import logging
logging.basicConfig(level=logging.DEBUG, format="(%(threadName)s) %(message)s")
def daemon_work():
logging.debug("Start")
time.sleep(3) # 3.main thread λ‘ Lock λκΉ
logging.debug("Exit")
def main():
t = threading.Thread(name="daemon work", target=daemon_work)
t.setDaemon(True) # 1.Daemon threadλ‘ μ€μ
t.start() # 2.Daemon thread μ€ν
logging.debug("Back to main thread") # 4.main thread μ’
λ£
if __name__=="__main__":
main()
μλ λ°λͺ¬ μ€λ λλ₯Ό ꡬννμ¬ μ€ννλ μ½λμ λλ€. λ°λͺ¬ μ€λ λλ₯Ό ꡬννλ λ°©λ²μ κ°λ¨ν©λλ€. μ€λ λμ setDaemon(boolean)λ©μλλ₯Ό μ¬μ©νμ¬ λ°λͺ¬μΌλ‘ μ€μ ν΄μ£Όλ©΄ λ©λλ€. μμ μ½λλ₯Ό μ€ννκ² λλ©΄ μλμ κ°μ κ²°κ³Όκ° μΆλ ₯λκ² λ©λλ€.
(daemon work) Start
(MainThread) Back to main thread
Daemon Threadμ Threadμ μ°¨μ΄
μμ μ½λμ μΆλ ₯ κ²°κ³Όλ₯Ό 보μλ©΄ μ΄μν μ μ΄ μμ΅λλ€. λ°λͺ¬ μ€λ λμ λ§μ§λ§ λ‘κΉ μ΄ μ€νλμ§ μμμ΅λλ€. κ·Έ μ΄μ λ λ°λͺ¬ μ€λ λμ κ²½μ° λ©μΈ νλ‘κ·Έλ¨ λ‘μ§μ΄ μ’ λ£λλ©΄ μλμΌλ‘ μ’ λ£λκΈ° λλ¬Έμ λλ€.
μ€λ λμ κ²½μ°, λ©μΈ νλ‘κ·Έλ¨μ λ‘μ§μ΄ μ’ λ£λλλΌλ μ€λ λκ° μ’ λ£λμ§ μλλ€λ©΄ μ€λ λκ° μ’ λ£λ λκΉμ§ λ©μΈ νλ‘κ·Έλ¨μ μ’ λ£λμ§ μκ³ κΈ°λ€λ¦¬κ² λ©λλ€. λ°λΌμ μΌμ μ£ΌκΈ°λ§λ€ λμμ λ°λ³΅νλ μμ μ ꡬνν λ, μΌλ° μ€λ λλ‘ κ΅¬ννκ² λλ©΄ μμ μ μ μ ν μ’ λ£μν€λ μμ μ λ³λλ‘ κ΅¬νν΄μΌν©λλ€.
νμ§λ§ λ°λͺ¬ μ€λ λλ λ©μΈ νλ‘κ·Έλ¨ λ‘μ§μ΄ μ’ λ£λλ€λ©΄ λ°λͺ¬ μ€λ λλ μλμΌλ‘ μ’ λ£λ©λλ€. λ°λΌμ λ°λͺ¬ μ€λ λλ₯Ό μ’ λ£μν€κΈ° μν λ³λμ μ²λ¦¬λ₯Ό μ κ²½μ°μ§ μμλ λ©λλ€. μ΄λ¬ν μ μΌλ‘ μΈνμ¬ μ€λ λμ μ κ·ΌνκΈ° μ΄λ ΅κ³ μλλλ‘ μ’ λ£μν€κΈ° μ΄λ €μ΄ μ μ΄ μμμλ λ°λͺ¬ μ€λ λλ₯Ό μ¬μ©νμ¬ λ°±κ·ΈλΌμ΄λ λμμ ꡬνν©λλ€.
Daemon Thread μμ μ’ λ£ κΈ°λ€λ¦¬κΈ°
λ°λͺ¬ μ€λ λλ λ©μΈ νλ‘κ·Έλ¨μ λ‘μ§μ΄ μ’ λ£λλ©΄ μλμΌλ‘ μ’ λ£λλ€κ³ μ€λͺ νμμ΅λλ€. νμ§λ§ νλ‘κ·Έλ¨μ λ°λΌμ λ°λͺ¬ μ€λ λμ μμ μ΄ μ’ λ£λμ΄μΌ νλ‘κ·Έλ¨μ μ’ λ£ ν΄μΌν κ²½μ°κ° μμ΅λλ€. μ΄λ¬ν κ²½μ° join()μ μ΄μ©νμ¬ λ°λͺ¬ μ€λ λκ° μ’ λ£λλ©΄ νλ‘κ·Έλ¨μ μ’ λ£ν μ μλλ‘ κ΅¬νν μ μμ΅λλ€.
def daemon_work():
logging.debug("Start")
time.sleep(3) # 3.main thread λ‘ Lock λκΉ
logging.debug("Exit")
def main():
t = threading.Thread(name="daemon work", target=daemon_work)
t.setDaemon(True) # 1.Daemon threadλ‘ μ€μ
t.start() # 2.Daemon thread μ€ν
logging.debug("Back to main thread") # 4.main thread μ’
λ£
t.join() # 5.Daemon thread μ’
λ£ λκΈ°
if __name__=="__main__":
main()
μμ λκ°μ νλ‘κ·Έλ¨μ λ§μ§λ§ join() λ©μλλ§ μΆκ°νμμ΅λλ€. μ΄μ²λΌ join()λ©μλλ₯Ό μ¬μ©νμ¬ μλνλλ‘ λ°λͺ¬ μ€λ λκ° μ’ λ£λκ³ λ©μΈ νλ‘κ·Έλ¨μ μ’ λ£νλλ‘ κ΅¬ννμμ΅λλ€.
(daemon work) Start
(MainThread) Back to main thread
(daemon work) Exit
4) Thread Event
νμ΄μ¬ threading λͺ¨λμμλ μ€λ λ κ°μ κ°λ¨ν ν΅μ μ μν΄ Eventλ₯Ό μ¬μ©ν©λλ€. μ€λ λμ Eventλ μ΄λ²€νΈλ₯Ό μ€μ , μ΄κΈ°ν, κΈ°λ€λ¦¬λ λμμ μ 곡ν©λλ€. μ΄λ¬ν λμλ€μ μ€λ λμμ μ¬μ©νμ¬ νΉμ 쑰건μ λ°λΌ μ€λ λλ₯Ό λμμν€λλ‘ μ μ΄νλλ° μ¬μ©ν μ μμ΅λλ€.
thread κ°μ κ°λ¨ν ν΅μ μ μν΄ μ¬μ©λλ κ°μ²΄
- is_set() : λ΄λΆ νλκ·Έκ° Trueλ©΄ κ·Έλλ§ True λ°ν
- set() : λ΄λΆ νλκ·Έλ₯Ό Trueλ‘ μ€μ
- clear() : λ΄λΆ νλκ·Έλ₯Ό Falseλ‘ μ€μ
- wait(timeout=None) : λ΄λΆ νλκ·Έκ° Trueκ° λ λκΉμ§ blockingν¨.
e.g.
import time
import logging
import threading
logging.basicConfig(level=logging.DEBUG, format="(%(threadName)s) %(message)s")
def thread1(e1, e2):
while not e1.isSet():
event = e1.wait(1)
logging.debug("Event status : (%s)", event)
if event:
logging.debug("e1 is set.")
time.sleep(3)
logging.debug("setting event e2")
e2.set()
def thread2(e2):
while not e2.isSet():
event = e2.wait(1)
logging.debug("Event status : (%s)", event)
if event:
logging.debug("e2 is set.")
def main():
e1 = threading.Event()
e2 = threading.Event()
t1 = threading.Thread(name="First Thread", target=thread1, args=(e1, e2))
t1.start()
t2 = threading.Thread(name="Second Thread", target=thread2, args=(e2,))
t2.start()
logging.debug("wait ...")
time.sleep(5) # λ€λ₯Έ μ€λ λλ‘ Lock λ°ν
logging.debug("set event e1")
e1.set()
time.sleep(5)
logging.debug("Exit")
if __name__=="__main__":
main()
μ νλ‘κ·Έλ¨μ λμμ νλ¦μ λ°λΌ μ 리νλ©΄ μλμ κ°μ΅λλ€.
λ©μΈ μ€λ λμμ μ΄λ²€νΈμΈ e1, e2μ μ€λ λμΈ t1, t2λ₯Ό κ°κ° μμ±νκ³ , "wait ..."λ¬Έμ λ‘κΉ νλ μ½λλ₯Ό λμνκ² λ©λλ€. κ·Έλ¦¬κ³ time.sleep(5)μμ λ€λ₯Έ μ€λ λκ° λμνκ² λκ³ , t1κ³Ό t2κ° λ°λ³΅νμ¬ λμνμ§λ§ e1κ³Ό e2κ° μ€μ λμ§ μμ isSet()λ¬Έμμ Falseλ₯Ό λ°ννμ¬ if event λΈλ‘μ μ€ννμ§ λͺ»νλ©° t1,t2κ° λ²κ°μκ°λ©° λμνκ² λ©λλ€.
κ·Έλ¬λ μ€, λ©μΈ μ€λ λμ time.sleep(5) λμμ΄ λλκ³ λ€μ Lockμ λκΈ°νμ¬ νλν ν, e1μ setν ν λ€μ sleepλ¬Έμ λ§λ Lockμ λ°λ©νκ² λ©λλ€. κ·Έλ¬λ©΄ λ€μ t1μμ Lockμ νλνμ¬ λμνκ² λκ³ , isSet()μμ Trueλ₯Ό λ°ννμ¬ e2μ μ΄λ²€νΈλ₯Ό setνλ λ‘μ§μ μννκ² λ©λλ€.
λ€μμΌλ‘λ t2μμλ isSet()μ΄ Trueλ₯Ό λ°ννκ³ , μ€λ λλ₯Ό μ’ λ£νλ©° λ©μΈ μ€λ λκ° μ€νλλ©° νλ‘κ·Έλ¨μ΄ μ’ λ£λκ² λ©λλ€.
(MainThread) wait ...
(First Thread) Event status : (False)
(Second Thread) Event status : (False)
...
(MainThread) set event e1
(First Thread) Event status : (True)
(First Thread) e1 is set.
(Second Thread) Event status : (False)
...
(First Thread) setting event e2
(Second Thread) Event status : (True)
(Second Thread) e2 is set.
(MainThread) Exit
μ΄μ²λΌ threading λͺ¨λμμ μ 곡νλ Eventλ₯Ό μ¬μ©νμ¬ μ¬λ¬ μ€λ λκ° ν΅μ νλ©° κ° μ€λ λλ€μ μ€ν νλ¦μ μ μ΄ν μ μμ΅λλ€. μ΄μ κ°μ μ΄λ²€νΈλ νλ‘κ·Έλ¨μ νλ¦μ μ μ΄νμ¬ μλͺ» μ¬μ©νλ κ²½μ° νλ‘κ·Έλ¨μ΄ 루νμ λΉ μ§κ±°λ μλͺ»λ νλ¦λλ‘ μ€νλ μ μμΌλ―λ‘ λ©΄λ°ν λΆμνκ³ μ€κ³νμ¬ μ¬μ©ν΄μΌ ν©λλ€.
μ°Έκ³
- [Blog] λκΈ°νλ₯Ό μν threading.Event(): https://kinggodrobotics.tistory.com/9
- https://ssungkang.tistory.com/entry/python-GIL-Global-interpreter-Lock%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C
- https://it-eldorado.tistory.com/160