싱글톤 디자인 패턴
- 싱글톤 디자인 패턴은 글로벌하게 접근 가능한 하나의 객체를 제공하는 패턴
- 동일한 리소스에 대한 동시 요청의 충돌을 방지하기 위해 하나의 인스턴스를 공유하는 작업에 주로 사용
- 싱글톤 패턴은 객체 생성의 관점과 객체의 상태의 관점에서 방식의 차이가 존재
게으른 초기화(객체 생성의 관점)
- 싱글톤 패턴을 기반으로 하는 초기화 방식
- 모듈을 임포트 할 때 아직 필요하지 않은 시점에 실수로 객체를 미리 생성하는 경우가 있음
- 게으른 초기화 방식은 인스턴스를 꼭 필요할 때 생성
- 게으른 초기화 기본 코드
class Singleton(object):
__instance = None
def __init__(self):
if not Singleton.__instance:
print("__init__ method called")
else:
print("Instance already created:", self.getInstance())
@classmethod
def getInstance(cls):
if not cls.__instance:
cls.__instance = Singleton()
return cls.__instance
s = Singleton()
print("Object created", Singleton.getInstance())
s1 = Singleton()
모노스테이트 싱글톤 패턴
- 객체의 생성 여부보다는 객체의 상태와 행위가 더 중요하다고 여기는 싱글톤 기반 디자인 패턴
- 이름 그대로 모든 객체가 같은 상태를 공유하는 패턴
- 모노스테이트 싱글톤 패턴의 기본 코드
class Borg(object):
__shared_state = {"1": "2"}
def __init__(self):
self.x = 1
self.__dict__ = self.__shared_state
pass
b = Borg()
b1 = Borg()
b.x = 4
print("Borg Object 'b': ", b)
print("Borg Object 'b1': ", b1)
print("Borg Object 'b': ", b.__dict__)
print("Borg Object 'b': ", b1.__dict__)
내가 경험한 싱글톤 패턴
아주 운이 좋게도 위에서 언급한 두 가지 싱글톤 디자인 패턴을 회사 코드에서 경험 할 수 있었다.
- 게으른 초기화 싱글톤 패턴
- 이는 불필요한 메모리 사용을 줄이기 위해 채택된 방식으로 많은 개발자들이 사용하고 있다.
- 실제 다양한 프로젝트에서 http 객체, db 객체, logging객체 등 빈번하게 호출 되는 객체를 대상으로 싱글톤 패턴이 사용되고 있었다.
- 프로그램이 실행되고 해당 객체가 처음 호출된 이후 요청이 있을 때마다 같은 객체가 return 되면서 불필요한 리소스 낭비를 줄이고 있었다.
- 예전에 팀장님께서 내 코드를 보시더니 "이러면 객체가 계속 생성됩니다 싱글톤 패턴에 대해 공부해보세요"라고 말씀해주신적이 있는데 그때는 생성되면 뭐 어때..? 라고 생각해었는데 이제서야 조금은 이해가 되었다ㅎ..
- 모노스테이트 싱글톤 패턴
- 멀티프로세싱 프로그램을 통해 모노스테이트 싱글톤 패턴을 처음 보게 되었다.
- 실제 프로세스 전체가 sharedData라는 하나의 객체를 공유하면서 모두가 같은 상태를 공유할 수 있었다.
- 실제 파이썬에는 멀티프로세싱 공유 객체를 위한 multiprocessing.Manager라는 라이브러리가 있다.
- 여기서 중요한 점은 전체의 프로세스가 같은 객체의 상태를 공유해야한다. 그렇기 때문에 예를 들어, 1번 프로세스가 공유 자원에 접근 하여 어떠한 작업을 진행 할 때는 다른 프로세스는 잠시 대기를 해야한다.
- 이러한 이슈를 mutex(뮤텍스)와 semaphore(세마포어)라고 한다.
- 뮤텍스: 공유 리소스에 한 번에 하나의 스레드만 접근 할 수 있도록 하는 상호 배제 동시성 제어 정책
- 세마포어: 1보다 큰 수로 시작. 한번에 자원에 접근할 수 있는 스레드의 수
- 파이썬에 multiprocessing 패키지에는 위의 문제들을 예방하고자 Lock과 Semarphore 모듈이 있다.
- 따라서 전체의 공유 자원에 접근 할 때는 아래와 같이 시도한다. 예를 들어 공유 자원에 대한 어떠한 post 작업이 있을 때
def post(self, category, key, value):
self._lock.acquire()
if not self._sharedData.get(category):
self._sharedData[category] = {}
temp = self._sharedData.get(category)
temp.update({key: value})
self._sharedData[category] = temp
self._lock.release()
- 접근을 시도하는 시점에 acquire() 메소드를 사용하여 다른 프로세스의 접근을 막고, 작업이 종료된 이후 release() 매소드를 통해 다른 프로세스의 접근을 허용한다.
마무리
- 왜 많은 사람들이 싱글톤 디자인 패턴을 프로그래밍에 채택하고 사용하는지 알게 되었다.
- 객체의 생성의 관점과 컴퓨팅 리소스 관점에서 보았을 때는 컴퓨팅 리소스는 한정적이고 그것을 최대한 활용하기 위한 아주 효율적인 방법이다.
- 또한 객체의 상태의 관점에서 보았을 때 모두가 하나의 객체 상태를 공유하게 되면서 멀티 프로세스 프로그램에서 다양한 방법으로 활용할 수 있다. (단 모두가 전체의 공유자원에 접근할 수 있다는 것은 또 다른 이슈가 발생할 여지가 있기 때문에 항상 더 신중해야한다)