Ein Mutex, kurz für “Mutual Exclusion” (zu Deutsch: “gegenseitiger Ausschluss”), ist ein Mechanismus in der Informatik, der dazu dient, den gleichzeitigen Zugriff mehrerer Prozesse auf gemeinsam genutzte Ressourcen zu kontrollieren. Das Hauptziel eines Mutex besteht darin, sicherzustellen, dass nur 1 Prozess auf die geschützte Ressource zugreifen kann, um inkonsistente Daten oder unerwünschte Wettlaufbedingungen, sogenannte Race-Conditions zu verhindern.
Der Mutex-Mechanismus im Detail
Ein Mutex arbeitet in der Regel nach dem Prinzip “Lock” und “Unlock”. Wenn ein Prozess eine geschützte Ressource verwenden möchte, versucht er, das Mutex zu sperren (Lock). Wenn das Mutex bereits von einem anderen Prozess gesperrt ist, wird der anfragende Prozess blockiert und muss warten, bis das Mutex freigegeben wird. Wenn der Prozess fertig ist, gibt er das Mutex frei (Unlock), damit andere Prozesse darauf zugreifen können.
Mutexe sind in Multithreading-Anwendungen und parallelen Programmen von entscheidender Bedeutung, um Datenkonsistenz und die Vermeidung von Race-Conditions sicherzustellen. Es gibt verschiedene Implementierungen von Mutexen, darunter softwarebasierte Mutexe und hardwareunterstützte Mutexe, je nach den Anforderungen und der Architektur des Systems.
Python und das Problem mit PyWin32
Arbeitet man mit Python unter Windows, benötigt man das PyWin32-Modul um dann über PyWin32Event auf die Mutex-Funktion des Betriebsystems zuzugreifen. Leider benötigt PyWin32 je nach Version spezifische C++ Runtime Packages, eine spezielle Pfad-Konfiguration und je nach Anwendung Admin-Rechte. Arbeitet man mit einem embedded Python-Interpreter, machen es diese Voraussetzungen oft unmöglich PyWin32 sauber zu installieren, bzw. die Installation portabel zu halten.
Die Lösung: Mutex per CTypes
Die folgende Python-Klasse implementiert Mutex über direkte Windows Kernel-Aufrufe via CTypes, komplett ohne PyWin32 und ohne weitere Abhängigkeiten:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# Win32Mutex # # PyWin32-free Mutex. # # (c)2023 Harald Schneider import ctypes, signal, sys class Win32Mutex: def __init__(self, mutexName): self.version = '1.0.3' self.mutexName = f"Global\\{mutexName}" self.mutex = None def create(self): """ Create Mutex. :return: Boolean """ self.mutex = ctypes.windll.kernel32.CreateMutexA(None, False, self.mutexName) if ctypes.windll.kernel32.GetLastError() == 183: self.release() return False return True def release(self): """ Release Mutex. :return: """ if self.mutex: try: ctypes.windll.kernel32.ReleaseMutex(self.mutex) except: pass ctypes.windll.kernel32.CloseHandle(self.mutex) def interruptHandler(self, sig, frame): """ This is triggered by CTRL-C and exits the complete app. """ self.release() sys.exit(0) def addInterruptHandler(self): """ Installs a CTRL-C handler :return: """ signal.signal(signal.SIGINT, self.interruptHandler) if __name__ == '__main__': import time # Test concurrent processes: # Run this in 2 terminals and CTRL-C the 1st one. # m = Win32Mutex('testMutex') m.addInterruptHandler() m2 = Win32Mutex('testMutex') m2.addInterruptHandler() if m.create(): while True: print("Running critical code ...") time.sleep(1) else: while not m2.create(): time.sleep(1) print("Waiting for mutex ...") if m2.create(): while True: print("Running other critical code ...") time.sleep(1) |
Zum Testen kann man das Script in 2 Terminals starten:
- Prozess 1 läuft durch, während Prozess 2 blockiert wird.
- Bricht man Prozess 1 per CTRL-C ab, beginnt Prozess 2 weiter zu laufen.
Die Funktionsweise ist simpel:
- Über .create() versucht der Prozess das Mutex zu erzeugen.
- So lange .create() False zurück gibt, muss der Prozess warten.
- Liefert .create() True, besitzt der Prozess das Mutex und darf seinen Code ausführen.
- Am Ende wird per .release() das Mutex für andere Prozesse freigegeben.
Bei unserem Test wird .release() über den CTRL-C Interrupthandler der Klasse getriggert.