"프로그래밍 언어의 개념과 흐름에 대한 고찰 - 파이썬답게 코딩하기" 학습중...


multiprocessing 기법에 대해 어렴풋이 알거나 몰랐던 내용들 몇가지 정리해봅니다.



## CPython에서는 스레드를 사용할 때 GIL(Global Interpreter Lock)의 영향으로 멀티 코어 환경에서는 제성능을 못낸다.
## 파이썬 공식문서에도 스레드보다는 멀티프로세싱을 권고함.
## 멀티 프로세싱을 하면 CPU 코어 갯수만큼 나눠서 일을 처리. GIL 영향을 안받음.
## 단점으로는 메모리를 많이 차지, 메모리 공유를 위한 IPC 설정(Inter Process Communication)필요.



(예문)


# import os
# import time
# import multiprocessing
#
# def worker(count):
# print("name : %s, argument : %s" % (multiprocessing.current_process().name, count))
# print("parent pid : %s, pid : %s" % (os.getppid(), os.getpid()))
# print("")
#
# def main():
# for i in range(5):
# p = multiprocessing.Process(target = worker, name = "process %i" % i, args = (i,))
# p.start()
# time.sleep(1)
#
# if __name__ == "__main__":
# main()



(실행결과)


# name : process 0, argument : 0
# parent pid : 15076, pid : 15384
#
# name : process 1, argument : 1
# parent pid : 15076, pid : 16480
#
# name : process 2, argument : 2
# parent pid : 15076, pid : 17464
#
# name : process 3, argument : 3
# parent pid : 15076, pid : 17352
#
# name : process 4, argument : 4
# parent pid : 15076, pid : 14260

## 스레드와 비슷한 구현 방법. 부모 프로세스의 id 15076으로 같으나 process id는 각기 다름.



(예문)


## multiprocess의 클래스 구현

# import os
# import time
# import multiprocessing
#
# class Worker(multiprocessing.Process) :
# def __init__(self, name, args):
# multiprocessing.Process.__init__(self)
# self.name = name
# self.args = args
#
# def run(self):
# print("name : %s, argument : %s" % (self.name, self.args[0]))
# print("parent pid : %s, pid : %s" % (os.getppid(), os.getpid()))
# print("")
#
# def main() :
# for i in range(5) :
# p = Worker(name = "process %i" % i, args = (i,))
# p.start()
# time.sleep(1)
#
# if __name__ == "__main__" :
# main()



(실행결과)


# name : process 0, argument : 0
# parent pid : 18052, pid : 11152
#
# name : process 1, argument : 1
# parent pid : 18052, pid : 7964
#
# name : process 2, argument : 2
# parent pid : 18052, pid : 3316
#
# name : process 3, argument : 3
# parent pid : 18052, pid : 3948
#
# name : process 4, argument : 4
# parent pid : 18052, pid : 14424

## 클래스 만드는 법도 스레드와 동일하다.
## multiprocessing 모듈은 프로세스를 띄우는 방식이기 때문에 띄워진 자식 프로세스에서 메인 모듈을 import 할 수도 있다.
## 그럴 경우 재귀적으로 반복 실행될 수 있으므로 "__main__"을 확인해서 재귀적으로 실행되지 않도록 보호해야 한다.



"프로그래밍 언어의 개념과 흐름에 대한 고찰 - 파이썬답게 코딩하기" 학습중...


동시성과 관련한 thread 기법에 대해 어렴풋이 알거나 몰랐던 내용들 몇가지 정리해봅니다.



## Lock Mutex 그리고 Semaphore
## 락은 자원에 접근할 때 무결성을 보장하기 위해 사용. 하나의 프로세스 안에서만 유효.
## 뮤텍스는 락과 유사하지만 여러 프로세스 사이에서 사용됨.
## 세마포어는 뮤텍스를 확장한 것. 락과 뮤텍스는 한번에 하나의 스레드만을 점유할 수 있으나 세마포어는 정해진 개수만큼 스레드 점유
## 만약 세마포어를 3개 설정하면 시스템 전체를 통틀어 3개의 스레드를 이용할 수 있다. 1개로 설정하면 뮤텍스와 같은 역할
# import time
# import logging
# import threading
#
# logging.basicConfig(level = logging.DEBUG, format = "(%(threadName)s) %(message)s")
# class ResourcePool() :
# def __init__(self):
# self.active_thread_list = []
# self.lock = threading.Lock()
#
# def use(self, name):
# with self.lock:
# self.active_thread_list.append(name)
# logging.debug("list of threads in resource pool : %s", self.active_thread_list)
#
# def unuse(self, name):
# with self.lock:
# self.active_thread_list.remove(name)
# logging.debug("list of threads in resource pool : %s", self.active_thread_list)
#
# def worker(semaphore, pool):
# logging.debug("waiting to enter the pool")
# with semaphore: ## 세마포어도 lock과 같이 with 구문 사용된다.
# logging.debug("enter the pool")
# thread_name = threading.currentThread().getName()
# pool.use(thread_name)
# time.sleep(1)
# pool.unuse(thread_name)
#
# def main():
# pool = ResourcePool()
# semaphore = threading.Semaphore(5)
# for i in range(7):
# t = threading.Thread(target = worker, name = ("thread-%s" %i), args = (semaphore, pool))
# t.start()
#
# if __name__ == "__main__":
# main()



실행 결과


# [실행 결과]
# (thread-0) waiting to enter the pool
# (thread-0) enter the pool
# (thread-1) waiting to enter the pool
# (thread-0) list of threads in resource pool : ['thread-0']
# (thread-2) waiting to enter the pool
# (thread-1) enter the pool
# (thread-3) waiting to enter the pool
# (thread-4) waiting to enter the pool
# (thread-2) enter the pool
# (thread-5) waiting to enter the pool
# (thread-1) list of threads in resource pool : ['thread-0', 'thread-1']
# (thread-6) waiting to enter the pool
# (thread-3) enter the pool
# (thread-4) enter the pool
# (thread-2) list of threads in resource pool : ['thread-0', 'thread-1', 'thread-2']
# (thread-3) list of threads in resource pool : ['thread-0', 'thread-1', 'thread-2', 'thread-3']
# (thread-4) list of threads in resource pool : ['thread-0', 'thread-1', 'thread-2', 'thread-3', 'thread-4']
# (thread-0) list of threads in resource pool : ['thread-1', 'thread-2', 'thread-3', 'thread-4']
# (thread-5) enter the pool
# (thread-4) list of threads in resource pool : ['thread-1', 'thread-2', 'thread-3']
# (thread-3) list of threads in resource pool : ['thread-1', 'thread-2']
# (thread-6) enter the pool
# (thread-2) list of threads in resource pool : ['thread-1']
# (thread-1) list of threads in resource pool : []
# (thread-5) list of threads in resource pool : ['thread-5']
# (thread-6) list of threads in resource pool : ['thread-5', 'thread-6']
# (thread-6) list of threads in resource pool : ['thread-5']
# (thread-5) list of threads in resource pool : []




## Thread Local Data
## 락과 세마포어로 자원을 공유하였으나, 이번에는 공유하지 않고 각각의 스레드에서만 사용될 자원에 대해서도 봐야한다.

# import logging
# import threading
#
# logging.basicConfig(level = logging.DEBUG, format = "(%(threadName)s) %(message)s")
#
# def print_local_data(local_data):
# try:
# data = local_data.index
# except:
# logging.debug("value not set")
# else:
# logging.debug("value : %s" % data)
#
# def set_local_data(local_data, index):
# print_local_data(local_data)
# local_data.index = index
# print_local_data(local_data)
#
# def main():
# local_data = threading.local() ## threading 모듈에서 제공하는 local 클래스를 이용해서 local_data 변수 선언.
# print_local_data(local_data)
# local_data.index = 0
# print_local_data(local_data)
# for i in range(5): ## 5개의 스레드를 띄우고 local_data와 스레드의 인덱스를 매개변수로 전달.
# t = threading.Thread(target = set_local_data, name = ("thread-%s" %i), args = (local_data, i + 1))
# t.start()
#
# if __name__ == "__main__":
# main()



실행 결과


# [실행 결과]
# (MainThread) value not set
# (MainThread) value : 0
# (thread-0) value not set
# (thread-0) value : 1
# (thread-1) value not set
# (thread-2) value not set
# (thread-1) value : 2
# (thread-3) value not set
# (thread-2) value : 3
# (thread-4) value not set
# (thread-3) value : 4
# (thread-4) value : 5

## [맺음말]
## 실행 결과를 보면 local_data를 공유하지 않는다. 스레드별 고유의 데이터를 사용하려면 스레드 모듈의 local 클래스 활용.
## GIL의 영향으로 성능 발휘에 어려움. 비결정적, 비순차적이기 때문에 진행을 예측하기 어려워 디버깅도 어려움.



"프로그래밍 언어의 개념과 흐름에 대한 고찰 - 파이썬답게 코딩하기" 학습중...


동시성과 관련한 thread 기법에 대해 어렴풋이 알거나 몰랐던 내용들 몇가지 정리해봅니다.



## Thread Re-entrant Lock 사용법

# import time
# import logging
# import threading
#
# logging.basicConfig(level = logging.DEBUG, format = "(%(threadName)s) %(message)s")
#
# RESOURCE = 0
#
# def set_reverse(lock) :
# logging.debug("start batch")
# ## with 구문을 통해 context manager object 를 사용한다면 try ~ finally 구문을 사용하지 않고도 관리하기 좋은 코드를 작성할 수 있다.
# ## 보통이라면 try: lock finally: pass로 구현해야 할 것을 with 하나로 해결. file open동작에서 close 생략 가능한 것 처럼.
# with lock :
# logging.debug("grap lock")
# if RESOURCE == 0 :
# set_one(lock, True)
# else :
# set_zero(lock, True)
# logging.debug("reserved")
#
# def set_zero(lock, end = False) :
# logging.debug("start set zero")
# while True :
# with lock :
# logging.debug("grap lock and set RESOURCE to 0.")
# RESOURCE = 0
# time.sleep(0.5)
# time.sleep(1)
# if end :
# break
#
# def set_one(lock, end = False) :
# logging.debug("start set one")
# while True :
# with lock :
# logging.debug("grap lock and set RESOURCE to 1.")
# RESOURCE = 1
# time.sleep(0.5)
# time.sleep(1)
# if end :
# break
#
# def main() :
# lock = threading.RLock()
# zero = threading.Thread(target = set_zero, name = "zero", args = (lock,))
# zero.setDaemon(True)
# zero.start()
#
# one = threading.Thread(target = set_one, name = "one", args = (lock,))
# one.setDaemon(True)
# one.start()
#
# time.sleep(6)
#
# reverse = threading.Thread(target = set_reverse, name = "reverse", args = (lock,))
# reverse.start()
#
# if __name__ == "__main__" :
# main()

# (zero) start set zero
# (zero) grap lock and set RESOURCE to 0.
# (one) start set one
# (one) grap lock and set RESOURCE to 1.
# (zero) grap lock and set RESOURCE to 0.
# (one) grap lock and set RESOURCE to 1.
# (zero) grap lock and set RESOURCE to 0.
# (one) grap lock and set RESOURCE to 1.
# (zero) grap lock and set RESOURCE to 0.
# (one) grap lock and set RESOURCE to 1.
# (reverse) start batch
# (reverse) grap lock
# (reverse) start set one
# (reverse) grap lock and set RESOURCE to 1.
# (reverse) reserved
# (zero) grap lock and set RESOURCE to 0.
## Lock을 잡은 상태에서 다른 함수나 클래스를 호출할 때 다시 그 Lock을 잡아야 할 경우에 사용한다.
## RLock Lock에 비해 처리해야 하는 프로세스가 더 있기 때문에 성능면에서 조금 더 느리다.




## Thread Condition
## 스레드의 락과 이벤트를 섞은 듯한 기능. 이 기능 사용시 모든 스레드가 락을 잡은 것처럼 멈춤. notify받으면 다시 동작.
# import time
# import logging
# import threading
#
# logging.basicConfig(level = logging.DEBUG, format = "(%(threadName)s) %(message)s")
#
# def receiver(condition) :
# logging.debug("start receiver")
# with condition :
# logging.debug("waiting...")
# condition.wait()
# time.sleep(1)
# logging.debug("end")
#
# def sender(condition) : ## receiver들에게 noti를 주는 기능
# logging.debug("start sender")
# with condition :
# logging.debug("send notify")
# condition.notifyAll()
# logging.debug("end")
#
# def main() :
# condition = threading.Condition() ## 스레드 5개 실행
# for i in range(5) :
# t = threading.Thread(target = receiver, name = "receiver %s" %i, args = (condition,))
# t.start()
#
# send = threading.Thread(target = sender, name = "sender", args = (condition,))
# time.sleep(1)
# with condition :
# condition.notify(1)
#
# time.sleep(3)
# send.start()
#
# if __name__ == "__main__" :
# main()

# (receiver 0) start receiver
# (receiver 0) waiting...
# (receiver 1) start receiver
# (receiver 2) start receiver
# (receiver 1) waiting...
# (receiver 3) start receiver
# (receiver 4) start receiver
# (receiver 2) waiting...
# (receiver 3) waiting...
# (receiver 4) waiting...
# (receiver 0) end
# (sender) start sender
# (sender) send notify
# (sender) end
# (receiver 1) end
# (receiver 3) end
# (receiver 4) end
# (receiver 2) end



'파이썬(PYTHON)' 카테고리의 다른 글

파이썬의 멀티프로세스(multiprocessing)1  (0) 2020.08.28
파이썬의 스레드(thread)4  (0) 2020.08.27
파이썬의 스레드(thread)2  (0) 2020.08.25
파이썬의 스레드(thread)1  (0) 2020.08.24
파이썬의 기본 문법3  (0) 2020.08.23

+ Recent posts