Skip to content

Instantly share code, notes, and snippets.

@tyalie
Last active August 21, 2020 11:42
Show Gist options
  • Save tyalie/f4b8ff903961ce0564c76d45f9a7ee8c to your computer and use it in GitHub Desktop.
Save tyalie/f4b8ff903961ce0564c76d45f9a7ee8c to your computer and use it in GitHub Desktop.
An AdvancedSingleton for Multiple file management

I hit on a problem, were an existing code base with excessive multithreading (server) made excessive use of global variables for file handling. But this solution would crash the moment we enabled async executionn in the server application.

This class is just the fundament. In the project itself I don't return the file instance directly, but hidden behind another class that allows me read and write the original file threadsafe.

As such this class only handles the file open and close operations in a thread safe manner. The class garantuees that a file is only opened once. If multiple instances want to access a file they all get the same file instance.

But the class also closes a file instance if nobody wants to access it anymore.

from threading import Lock, Thread
from time import sleep
class AdvancedSingleton(object):
__instances = {}
__instances_count = {}
__lock = Lock()
@staticmethod
def open(file):
with AdvancedSingleton.__lock:
print(f"New called with '{file}'", end="")
if file in AdvancedSingleton.__instances:
print(" [already exists]")
inst = AdvancedSingleton.__instances[file]
else:
print(" [new]")
inst = AdvancedSingleton(file)
AdvancedSingleton.__instances[file] = inst
AdvancedSingleton.__instances_count[file] = 0
AdvancedSingleton.__instances_count[file] += 1
return inst
def __init__(self, file):
"""
Using open I can maintain following code structure:
```
with FileWrapper.open("my_file.h5") as f:
with f.read() as reader:
reader["tables"]
```
"""
print(f"init called with '{file}'")
self.file = file
def __str__(self):
return f"{repr(self)} + '{self.file}' ({AdvancedSingleton.__instances_count[self.file]} are open)"
def __enter__(self):
# nothing to change
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def close(self):
with AdvancedSingleton.__lock:
if self.file not in AdvancedSingleton.__instances_count or self.file not in AdvancedSingleton.__instances:
return
print(f"Closing instance {self}")
AdvancedSingleton.__instances_count[self.file] -= 1
if AdvancedSingleton.__instances_count[self.file] == 0:
print(f"Closing file instance {self}")
del AdvancedSingleton.__instances_count[self.file]
del AdvancedSingleton.__instances[self.file]
def __del__(self):
self.close()
access_dict = {0: 'a', 1: 'b', 2: 'a', 3: 'c'}
def test_singleton(index):
file = access_dict[index]
if file == 'a' or file == 'b':
instance = AdvancedSingleton.open(file)
print(f"Successfully got {instance}")
if access_dict[index] == 'a':
sleep(1)
# force garbage collector to collect instance with 'b'
if access_dict[index] == 'a':
instance.close()
print(f"Finished for {file}")
else:
with AdvancedSingleton.open(file) as instance:
print(f"Successfully got {instance}")
print(f"Finished for {file}")
def worker(index):
# wrapper so that `instance` is only local => garbage collection might happen
test_singleton(index)
# maybe trigger garbage collector?
sleep(2)
print(f"Finished worker {index}")
'''
To be honest. I've never achieved garbage collection
before the whole program shut down. But I gave it the
possibility.
'''
parallel = True
if __name__ == '__main__':
if parallel:
threads = []
for i in range(4):
t = Thread(target=worker, args=(i, ))
threads.append(t)
t.start()
else:
for i in range(3):
test_singleton(i)
print("Done")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment