Singleton pattern for Python (single and multi-threaded)

Authors

Goals:

  • Provide single point of access, to a one and only instance of a class
  • Support lazy (on-demand) instantiation
  • Support single and multi-threaded RT environment

Challenges:

  • Python does not support constructor hiding (i.e. marking as private)
  • Proving I’m right – CPython does not really support multi-threading, due to its Global Interpreter Lock

What’s good here?

  • Instead of constructor hiding we’ll raise an exception
  • Double checked locking
  • Using threading.Lock as a thread-level lock
  • Instance ID is fetched from the id() method. In CPython it’s the instance memory address
  • Instance gets a unique timestamp, which will help us prove it’s a “one of a kind”
  • “PrintFlushed” – a method for custom Printing in a multi-threaded environment
  • “Reset” – a method for clearing the instance, aiding testability

class Singleton:

    _instance = None
    _lock = threading.RLock()
    def __init__(self):
        self.timestamp = str(time.time())
        # Cannot hide ctor, so raise an error from 2nd instantiation
        if (Singleton._instance != None):
            raise(‘This is a Singleton! use Singleton.GetInstance method’)
        # Simulate ctor delay
        time.sleep(2)
        Singleton._instance = self
        PrintFlushed(‘Singleton was created by thread ID ‘ +  str(threading.current_thread().ident))
    @staticmethod
    def GetInstance():
        if (Singleton._instance == None):
            Singleton._lock.acquire()
            if (Singleton._instance == None):
                Singleton._instance = Singleton()
            Singleton._lock.release()
        return Singleton._instance
    @staticmethod
    def Reset():
        Singleton._instance = None
lockPrint = threading.Lock()
def PrintFlushed(sMessage,):
    lockPrint.acquire()
    print(sMessage)
    print(‘————————————-‘)
    sys.stdout.flush()
    lockPrint.release()

single threaded testing

Singleton.Reset()
# Cannot prevent next line from running for the 1st time, but no real harm, also
# ins0 = Singleton()
ins1 = Singleton.GetInstance()
# Would raise error:
# ins4 = Singleton()
ins2 = Singleton.GetInstance()
if ins1 == ins2 :
    PrintFlushed(‘Thread ID ‘ + str(threading.current_thread().ident)
           + ‘ got instance ID ‘ + str(id(ins1))
           + ‘\nInstance time stamp = ‘ + ins2.timestamp)
    PrintFlushed(‘Thread ID ‘ + str(threading.current_thread().ident)
           + ‘ got instance ID ‘ + str(id(ins1))
           + ‘\nInstance time stamp = ‘ + ins2.timestamp)

multi threaded testing

def GetInstanceThreaded():
    instance = Singleton.GetInstance()
    PrintFlushed(‘Thread ID ‘ + str(threading.current_thread().ident)
           + ‘ got instance ID ‘ + str(id(instance))
           + ‘\nInstance time stamp = ‘ + instance.timestamp)
Singleton.Reset()
lstProcess = []
for i in range(3):
    thread = threading.Thread(target=GetInstanceThreaded)
    lstProcess.append(thread)
    thread.start()
# join all remaining running threads
for thread in lstProcess:
    thread.join()
multi-threaded – says who?
I tested it again with IronPython, a version of Python that, among other things, does not enforce the Global Interpreter Lock
Print results:

Test it with a single thread
————————————-
Singleton was created by thread ID 1
————————————-
Thread ID 1 got instance ID 43
Instance time stamp = 1370199234.00
————————————-
Thread ID 1 got instance ID 43
Instance time stamp = 1370199234.00
————————————-
Test it with multi threading
————————————-
Singleton was created by thread ID 9
————————————-
Thread ID 9 got instance ID 44
Instance time stamp = 1370199236.34
————————————-
Thread ID 8 got instance ID 44
Instance time stamp = 1370199236.34
————————————-
Thread ID 10 got instance ID 44
Instance time stamp = 1370199236.34
————————————-

 

Some links:

1 Comment

Comments RSS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: