Singleton thread-safe en Python


Le singleton est probablement le design pattern le plus utilisé pour limiter la consommation des ressources comme le nombre de connexions aux bases de données ou tout autre service. Son implémentation est relativement simple mais elle peut rapidement devenir source d’ennuis si le code doit être thread-safe.
Un code thread-safe est généralement garanti par utilisation d’un système de synchronisation comme un verrou dont Python possède grâce à sa primitive Lock du module threading, mais une autre astuce est nécessaire pour un support thread-safe.
Implémentation
1import logging
2import threading
3
4class MySingleton(object):
5 _instance: object = None
6 _lock: threading.Lock = threading.Lock()
7
8 def __new__(cls):
9 """Create a new instance"""
10 if MySingleton._instance is None:
11 with MySingleton._lock:
12 if MySingleton._instance is None:
13 MySingleton._instance = super(MySingleton, cls).__new__(cls)
14 MySingleton._instance._initialized = False
15
16 return MySingleton._instance
17
18 def __init__(self):
19 """Initialize the instance"""
20 if self._initialized is False:
21 self._logger = logging.getLogger(self.__class__.__name__)
22 self._count = 0
23 self._initialized = True
24
25 self._logger.debug("using instance: %s" % self._instance)
26
27 def increment(self):
28 """Increment the counter"""
29 self._count += 1
30
31 self._logger.debug("count = %d" % self._count)
L’astuce est donc d’utiliser un verrou combiné à un autre objet qui va conserver l’instance partagée de votre classe singleton.
Test
Pour tester son fonctionnement, lancez une console Python:
>>> from code import MySingleton
>>> logging.basicConfig(level=logging.DEBUG, handlers=[logging.StreamHandler()])
>>> a = MySingleton()
DEBUG:MySingleton:using instance: <__main__.MySingleton object at 0x7f8bee80c550>
>>> b = MySingleton()
DEBUG:MySingleton:using instance: <__main__.MySingleton object at 0x7f8bee80c550>
>>> a.increment()
DEBUG:MySingleton:count = 1
>>> a.increment()
DEBUG:MySingleton:count = 2
>>> b.increment()
DEBUG:MySingleton:count = 3
Comme vous pouvez le voir, les instances a et b sont identiques.