Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is this singleton implementation "not thread safe"?

1. The @Singleton decorator

I found an elegant way to decorate a Python class to make it a singleton. The class can only produce one object. Each Instance() call returns the same object:

class Singleton:
    """
    A non-thread-safe helper class to ease implementing singletons.
    This should be used as a decorator -- not a metaclass -- to the
    class that should be a singleton.

    The decorated class can define one `__init__` function that
    takes only the `self` argument. Also, the decorated class cannot be
    inherited from. Other than that, there are no restrictions that apply
    to the decorated class.

    To get the singleton instance, use the `Instance` method. Trying
    to use `__call__` will result in a `TypeError` being raised.

    """

    def __init__(self, decorated):
        self._decorated = decorated

    def Instance(self):
        """
        Returns the singleton instance. Upon its first call, it creates a
        new instance of the decorated class and calls its `__init__` method.
        On all subsequent calls, the already created instance is returned.

        """
        try:
            return self._instance
        except AttributeError:
            self._instance = self._decorated()
            return self._instance

    def __call__(self):
        raise TypeError('Singletons must be accessed through `Instance()`.')

    def __instancecheck__(self, inst):
        return isinstance(inst, self._decorated)

I found the code here: Is there a simple, elegant way to define singletons?

The comment at the top says:

[This is] a non-thread-safe helper class to ease implementing singletons.

Unfortunately, I don't have enough multithreading experience to see the 'thread-unsafeness' myself.

 

2. Questions

I'm using this @Singleton decorator in a multithreaded Python application. I'm worried about potential stability issues. Therefore:

  1. Is there a way to make this code completely thread-safe?

  2. If the previous question has no solution (or if its solution is too cumbersome), what precautions should I take to stay safe?

  3. @Aran-Fey pointed out that the decorator is badly coded. Any improvements are of course very much appreciated.


Hereby I provide my current system settings:
    >  Python 3.6.3
    >  Windows 10, 64-bit

like image 269
K.Mulier Avatar asked May 28 '18 12:05

K.Mulier


People also ask

Can we make singleton as thread safe?

Thread Safe Singleton: A thread safe singleton is created so that singleton property is maintained even in multithreaded environment. To make a singleton class thread safe, getInstance() method is made synchronized so that multiple threads can't access it simultaneously.

Can we implement multithreading in singleton class?

While we are using multithreading access to a singleton instance can be performed from various threads it could be a problem while constructing singleton instances. If you are in Singleton::Instance() and receive an interrupt, invoke Singleton::Instance() from another thread, you can see how you'd get into trouble.

What is a disadvantage for the Singleton pattern?

Disadvantages of a Singleton PatternUnit testing is more difficult (because it introduces a global state into an application). This pattern reduces the potential for parallelism within a program, because to access the singleton in a multi-threaded system, an object must be serialized (by locking).


1 Answers

I suggest you choose a better singleton implementation. The metaclass-based implementation is the most frequently used.

As for for thread-safety, nor your approach nor any of the ones suggested in the above link are thread safe: it is always possible that a thread reads that there is no existing instance and starts creating one, but another thread does the same before the first instance was stored.

You can use a with lock controller to protect the __call__ method of a metaclass-based singleton class with a lock.

import threading

lock = threading.Lock()

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            with lock:
                if cls not in cls._instances:
                    cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class SingletonClass(metaclass=Singleton):
    pass

As suggested by se7entyse7en, you can use a check-lock-check pattern. Since singletons are only created once, your only concern is that the creation of the initial instance must be locked. Although once this is done, retrieving the instance requires no lock at all. For that reason we accept the duplication of the check on the first call so that all further call do not even need to acquire the lock.

like image 120
Olivier Melançon Avatar answered Oct 20 '22 21:10

Olivier Melançon