Template Mutex
-
Seht euch das mal an.
Ich hab die Klasse heute gemacht, also wird sie wahrscheinlich noch einige Bugs haben, bzw. hoffentlich macht sie auch das richtige, oder zumindest das richtig,
was beschrieben ist *g*/* Die Template-Klasse Mutex: Der Mutex schützt Objekte vor gleichzeitigen Zugriffen verschiedener Threads. Diese Implementation arbeitet nicht mit Mutexes, die von verschiedenen Prozessen gleichzeitig verwendet werden kann. Ein Mutex kann auf 3 Arten aktiviert werden: 1.) Statischer Mutex: Es wird keine Instanz der Klasse benötigt. Das zu schützende Element wird den statischen Methoden als Referenz übergeben. Das Template Argument muss der Typ des zu schützenden Objektes sein. Beispiel: Mutex<int>::enable (x); -> erzeugt einen Mutex für ein int Element Mutex<int>::disable (x); -> gibt denselben Mutex wieder frei 2.) Allgemeiner Mutex: Der Mutex wird als Instanz erzeugt. Die Template-Argumente spielen keine Rolle, es ist ein default eingestellt, so dass leere <> benützt werden können. Dieser Mutex ist nicht an ein Objekt gebunden, sondern kann für verschiedene Aufgaben gleichzeitig benutzt werden. Die Elementfunktionen sind nicht statisch und benötigen keine Argumente. Der allgemeiner Mutex muss global sein, oder so implementiert, dass alle Funktionen, die ihn benutzen Zugriff auf genau diesen haben. Beispiel: Für alle Threads zugänglich: Mutex<> M; -> allgemeiner Mutex wird erzeugt. Thread 1: M.enable (); -> Mutex wird eingeschalten M.disable (); -> Mutex wird ausgeschalten Thread 2: M.enable (); -> Mutex wird eingeschalten M.disable (); -> Mutex wird ausgeschalten 3.) Nicht-statischer Mutex: Ein Mutex, der bei seiner Erzeugung an ein Element gebunden wird. Alternativ besteht die Möglichkeit, mithilfe der Memberfunktion "bind" ein Objekt an einen allgemeinen Mutex zu binden. Der allgemeine Mutex kann danach nicht wiederhergestellt werden. Der nicht-statische Mutex kann ebenfalls über die Funktion "bind" an ein anderes Objekt gebunden werden. Der vorherige Mutex wird dabei freigegeben. Das Template-Argument muss vom Typ des zu schützenden Objektes sein, welcher nicht gewechselt werden darf. Dieser Mutex muss nicht global sein, da ein Pool verwendet wird. Beispiel 1: Mutex<int> M(x); -> Variable x wird gebunden M.enable (); -> Mutex für x wird aktiviert M.disable (); -> Mutex für x wird deaktiviert M.bind (y); -> Variable y wird gebunden. M.enable (); -> Mutex für y wird aktiviert. M.disable (); -> Mutex für y wird deaktiviert. //////////////////////////// Beispiel 2: Mutex<> M; -> allgemeiner Mutex wird erzeugt M.bind (x); -> nicht möglich, außer x wäre vom default-Template Argument Mutex<int> M; -> allgemeiner Mutex mit Template Argument wird erzeut M.bind (x); -> M liefert Mutexes für alle int-Variablen Ein nicht-statischer Mutex kann jedoch nicht mehr in einen allgemeinen Mutex zurückgewandelt werden. Der statische Mutex und der nicht-statische Mutex verwenden intern beide denselben Mutex. Das heißt, folgendes wäre korrekt: Mutex<int>::enable (x); Mutex<int> M(x); M.disable (); Aber es ist natürlich nicht gut lesbar. */ struct dummy {}; template <typename T = dummy> class Mutex { static HANDLE intern; static std::map <T *, std::pair<void *, int> > used; HANDLE mutex; T *bound; public: static void enable (T &t) { if (intern == 0) intern = CreateMutex (0, 0, 0); WaitForSingleObject (intern, INFINITE); T *that = &t; if (used.find(that) == used.end() ) { used[that].first = CreateMutex (0, 0, 0); used[that].second = 0; } used[that].second++; ReleaseMutex (intern); WaitForSingleObject (used[that].first, INFINITE); } static void disable (T &t) { WaitForSingleObject (intern, INFINITE); T *that = &t; ReleaseMutex (used[that].first); used[that].second--; if (used[that].second == 0) { CloseHandle (used[that].first); used.erase (that); } ReleaseMutex (intern); } Mutex (T &bind) : bound(&bind) {} Mutex () : bound(0) { mutex = CreateMutex (0, 0, 0); } ~Mutex () { if (bound) disable (*bound); else CloseHandle (mutex); } void bind (T &b) { if (bound) disable (*bound); else CloseHandle (mutex); bound = &b; } void enable () { if (bound) enable (*bound); else WaitForSingleObject (mutex, INFINITE); } void disable () { if (bound) disable (*bound); else ReleaseMutex (mutex); } }; template <typename T> std::map <T *, std::pair<void *, int> > Mutex<T>::used; template <typename T> HANDLE Mutex<T>::intern;
btw. ist mein erster Versuch, ein bisschen MT zu machen, also verbessert mich bitte, da ich doch nicht weiß, auf was ich da alles achtgeben muss
MFG!
-
PS: is auf dem MS VC gemacht. hätte eigentlich noch was mit partieller t.-spezialisierung machen wollen, aber der kann das ja nicht. (und die umständliche simulierung wollte ich mir ersparen :))
-
woher kriegst du das zeugs immer? du bist ja total genial
-
Ich hab es mir jetzt nicht so genau angeschaut (ich hab es halt nicht so mit der stl), aber bei
CreateMutex (0, 0, 0);
liegt der Verdacht nahe, daß Du das alles nur innerhalb eines einzigen Prozesses verwendest. Und wenn dem so ist, solltest Du keine Mutexe verwenden. Wenn Du jetzt ein Lock anforderst (WaitForSingleObject), wird zunächst mal in den Kernel-Mode geschaltet. Das kostet Zeit. Diese Umschaltung passiert auch dann, wenn das Mutex nicht in Besitz eines anderen Threads ist.
Verwendest Du anstelle dessen eine CriticalSection, passiert die Umschaltung in den Kernel-Mode nur im Kollisionsfalle (intern wird dann auf ein Event gewartet). Dadurch wird die CriticalSection Blitzschnell, wenn kein anderer Thread auf die gemeinsame Ressource zugreift.
BTW: Wenn ich das richtig sehe, erzeugst Du im Ernstfall richtig viele Mutexe -> Ressourcenfresser. Dazu dann auch noch ein Gedanke: Erstelle ein fixed-size Array, sagen wir mal mit 128 Einträgen. Jetzt erstellst Du 128 Synchronisations-Objecte, gehalten im Array. Wenn jetzt ein Thread einen kritischen Abschnitt betreten soll, holt er sich irgendwo aus dem Array sein Synchronisations-Object und wartet auf dieses. Die Slot-Nummer erhälst Du, indem Du irgendwie einen Hash-Wert aus der ThreadID errechnest. Damit hättest Du dann die Gefahr der Kollision deutlich gemindert und den Ressourcen-Verbrauch komplett unter Kontrolle. Unter Du sparst Dir jedesmal eine Wait/Release Sequenz.
-
Thx, in CriticalSection umstellen wird nicht schwer werden.
Und das mit den zb 128 Synconisationsobjekten werd ich mir noch überlegen.
CreateMutex (0, 0, 0) -> jo, wollte ich aber noch ändern nur schon zu spät
wollts noch heute zum laufen bringen
Danke für die Tips!edit:
ähm, wie meinst du, ich erzeuge richtig viele s.-objekte?[ Dieser Beitrag wurde am 14.12.2002 um 00:40 Uhr von Noesis editiert. ]
-
ähm, wie meinst du, ich erzeuge richtig viele s.-objekte?
Vergiss das ganz schnell wieder. Ich hab jetzt nochmal genauer hingeschaut und siehe da: Du gibst gleich alles wieder schöne frei. Sorry. Allerdings wird es daduch erst recht zum Performance-Killer.
[edit]
<Dummes Zeug gesnippt>
Oh man, ich glaub, ich geh besser ins Bett ...
[/edit][ Dieser Beitrag wurde am 14.12.2002 um 01:16 Uhr von -King- editiert. ]
-
Hallo, ich hab mir die Ratschläge angeschaut und für gut befunden
hier nochmal die neueste Implementation:
(Schaut vielleicht lang aus, aber sind viele Kommentare)namespace Thread { //Ein privater namespace für Helfer Klassen des Mutexes //Enthaltene Klassen: //Dummy: Default Template Argument für den Mutex //Pool: Verwaltet Critical Sections, als Singleton implementiert //Die Beiden Privaten Klassen sollten nicht direkt benutzt werden, //die Klasse Mutex bietet alle Operationen auf einer höheren Ebene namespace MutexPrivate { struct Dummy {}; //Default Struktur für den allgemeinen Mutex //Der Pool verwaltet die vorhandenen Critical Sections. //Er ist als Singleton implementiert und verwaltet size //CRITICAL_SECTION Instanzen auf dem Heap. Bei Bedarf //werden resize CRITICAL_SECTION Objekte hinzugefügt. //Die Critical Sections werden automatisch initialisiert und //am Ende wieder freigegeben. class Pool { static Pool staticPool; int size; const int auto_resize; std::deque<CRITICAL_SECTION *> thePool; Pool (); Pool (const Pool &); const Pool &operator = (const Pool &); public: ~Pool (); CRITICAL_SECTION *getCriticalSection (); void releaseCriticalSection (CRITICAL_SECTION *); void resize (int); int available (); static Pool &getInstance (); }; Pool Pool::staticPool; Pool &Pool::getInstance () { return staticPool; } //Der Konstruktor legt size initialisierte Critical-Section //Objekte an, die in einem std::deque abgelegt werden. Pool::Pool () : size(128), auto_resize(16) { for (int i = 0; i < size; ++i) { CRITICAL_SECTION *tmp = new CRITICAL_SECTION; InitializeCriticalSection (tmp); thePool.push_back (tmp); } } //Der Destruktor gibt alle Critical-Sections ebenso //wie den Speicher frei. Pool::~Pool () { for (std::deque<CRITICAL_SECTION*>::iterator i = thePool.begin(); i != thePool.end(); ++i) { DeleteCriticalSection(*i); delete (*i); } } //Diese Funktion gibt zurück, wieviele Critical-Sections noch //verwendet werden können, bevor der Pool neue allokieren und //initialisieren muss int Pool::available () { return thePool.size(); } //getCriticalSection liefert eine freie Critial-Section aus dem Pool, //und lösch diese aus dem std::deque. Ist das std::deque leer, so //werden resize neue Critical-Sections in das std::deque geladen und //initialisiert. CRITICAL_SECTION * Pool::getCriticalSection () { if (available() == 0) { for (int i = 0; i < auto_resize; ++i) { CRITICAL_SECTION *tmp = new CRITICAL_SECTION; InitializeCriticalSection (tmp); thePool.push_back (tmp); } size += auto_resize; } CRITICAL_SECTION *ret = *thePool.begin(); thePool.erase (thePool.begin()); return ret; } //Eine nicht mehr benötigte Critical-Section kann händisch freigegeben werden, //also explizit mit DeleteCriticalSection und delete, oder sie wird dem Pool //zur Verwaltung zurückgegeben. Es empfiehlt sich jedoch nicht, neue Critical- //Sections dem Pool so zur Kontrolle zu übergeben, da diese auf jeden Fall //im Destruktor mit delete freigegeben werden. void Pool::releaseCriticalSection (CRITICAL_SECTION *cs) { thePool.push_back (cs); } //Dem Pool können auch explizit neue Elemente hinzugefügt werden. //resize kann den Pool vergrößern und verkleinern. //Wird der Pool verkleinert, werden Critical-Sections freigegeben, //gelöscht und aus dem std::deque entfernt. //Wird der Pool vergrößert, werden neue Critical-Sections erzeut, //initialisiert und dem std::deque hinzugefügt. void Pool::resize (int x) { int y = available(); if (x < 1) return; if (x < y) { for (int i = 0; i < x - y; ++i) { DeleteCriticalSection (*thePool.begin()); delete *thePool.begin(); thePool.erase (thePool.begin()); } } else { for (int i = 0; i < x - y; ++i) { CRITICAL_SECTION *tmp = new CRITICAL_SECTION; InitializeCriticalSection (tmp); thePool.push_back (tmp); } } size += x - y; } } /* Die Template-Klasse Mutex: Der Mutex schützt Objekte vor gleichzeitigen Zugriffen verschiedener Threads. Diese Implementation arbeitet nicht mit Mutexes, die von verschiedenen Prozessen gleichzeitig verwendet werden kann. Die Klasse arbeitet intern mit einer Pool aus Elementen vom Typ CRITICAL_SECTION. Ein Mutex kann auf 3 Arten aktiviert werden: 1.) Statischer Mutex: Es wird keine Instanz der Klasse benötigt. Das zu schützende Element wird den statischen Methoden als Referenz übergeben. Das Template Argument muss der Typ des zu schützenden Objektes sein. Beispiel: Mutex<int>::enable (x); -> erzeugt einen Mutex für ein int Element Mutex<int>::disable (x); -> gibt denselben Mutex wieder frei 2.) Allgemeiner Mutex (= ungebundener Mutex): Der Mutex wird als Instanz erzeugt. Die Template-Argumente spielen keine Rolle, es ist ein default eingestellt, so dass leere <> benützt werden können. Dieser Mutex ist nicht an ein Objekt gebunden, sondern kann für verschiedene Aufgaben gleichzeitig benutzt werden. Die Elementfunktionen sind nicht statisch und benötigen keine Argumente. Der allgemeiner Mutex muss global sein, oder so implementiert, dass alle Funktionen, die ihn benutzen Zugriff auf genau diesen haben. Von dieser Art des Mutexes können so viele erzeugt werden, wie benötigt. Beispiel: Für alle Threads zugänglich: Mutex<> M; -> allgemeiner Mutex wird erzeugt. Thread 1: M.enable (); -> Mutex wird eingeschalten M.disable (); -> Mutex wird ausgeschalten Thread 2: M.enable (); -> Mutex wird eingeschalten M.disable (); -> Mutex wird ausgeschalten 3.) Nicht-statischer Mutex (= gebundener Mutex): Ein Mutex, der bei seiner Erzeugung an ein Element gebunden wird. Alternativ besteht die Möglichkeit, mithilfe der Memberfunktion "bind" ein Objekt an im Nachhinein einen allgemeinen Mutex zu binden. Der allgemeine Mutex kann danach nicht wiederhergestellt werden. Der nicht-statische Mutex kann ebenfalls über die Funktion "bind" an ein anderes Objekt gebunden werden. Der vorherige Mutex wird dabei freigegeben. Das Template-Argument muss vom Typ des zu schützenden Objektes sein, welcher nicht gewechselt werden darf. Dieser Mutex muss nicht global sein, da ein Pool verwendet wird. Beispiel 1: Mutex<int> M(x); -> Variable x wird gebunden M.enable (); -> Mutex für x wird aktiviert M.disable (); -> Mutex für x wird deaktiviert M.bind (y); -> Variable y wird gebunden. M.enable (); -> Mutex für y wird aktiviert. M.disable (); -> Mutex für y wird deaktiviert. //////////////////////////// Beispiel 2: Mutex<> M; -> allgemeiner Mutex wird erzeugt M.bind (x); -> nicht möglich, außer x wäre vom default-Template Argument Mutex<int> M; -> allgemeiner Mutex mit Template Argument wird erzeut M.bind (x); -> M liefert Mutexes für alle int-Variablen Ein nicht-statischer Mutex kann jedoch nicht mehr in einen allgemeinen Mutex zurückgewandelt werden. Der statische Mutex und der gebundene Mutex verwenden intern beide denselben Mutex. Sie legen ihn in einer gemeinsam genutzten std::map ab, um später wieder auf ihn zugreifen zu können. Für jedes enable muss ein passendes disable vorhanden sein, dass bedeutet, auf das Enable eines gebundenen Mutex muss ein passendes Disable folgen. Wird die Bindung geändert, wird der alte Mutex automatisch deaktiviert. Ebenso durch einen Destruktoraufruf. Davor ist folglich kein explizites Disable nötig. */ template <typename T = MutexPrivate::Dummy> class Mutex { static bool init; static CRITICAL_SECTION intern; static std::map <T *, std::pair<CRITICAL_SECTION *, int> > used; CRITICAL_SECTION mutex; T *bound; //bound == 0, falls ein allgemeiner Mutex vorliegt bool boundActive; //First-Time Initializer static inline void initialize () { if (init == false) { init = true; InitializeCriticalSection (&intern); } } //Aktiviert die Critical Section zum Schutz statischer Member static inline void protectOn () { initialize (); EnterCriticalSection (&intern); } //Gibt die interne Critical Section wieder frei static inline void protectOff () { LeaveCriticalSection (&intern); } //gibt true zurück, falls der Mutex gebunden ist inline bool isBound () { return (bound) ? true : false; } //Es folgen Funktionen zur internen Kontrolle //eines ungebundenen Mutexes inline void unboundActivate () { EnterCriticalSection (&mutex); } inline void unboundDeactivate () { LeaveCriticalSection (&mutex); } inline void unboundInitialize () { InitializeCriticalSection (&mutex); } inline void unboundDelete () { DeleteCriticalSection (&mutex); } //Es folgen Funktionen zur internen Kontrolle //eines gebundenen Mutexes inline bool isActive () { return boundActive; } void boundActivate () { if (boundActive == false) { enable (*bound); boundActive = true; } } void boundDeactivate () { if (boundActive == true) { disable (*bound); boundActive = false; } } void boundInitialize (T &t) { boundDeactivate (); bound = &t; } void boundDelete () { boundDeactivate (); bound = 0; } public: //1. statische Hauptfunktion: enable static void enable (T &t) { T *that = &t; protectOn (); //Falls die passende CRITICAL_SECTION nicht existiert, wird sie erzeugt if (used.find(that) == used.end() ) { used[that].first = MutexPrivate::Pool::getInstance().getCriticalSection(); used[that].second = 0; } CRITICAL_SECTION *temp = used[that].first; used[that].second++; protectOff (); //Und nun den Mutex aktivieren EnterCriticalSection (temp); } //2. statische Hauptfunktion: disable static void disable (T &t) { T *that = &t; //Schutz für die statischen Member aktivieren protectOn (); //Mutex entsperren LeaveCriticalSection (used[that].first); used[that].second--; //Falls das der letzte Thread war, der auf die Critical Section von //t gewartet hat, kann die Critical Section entfernt werden if (used[that].second == 0) { MutexPrivate::Pool::getInstance().releaseCriticalSection (used[that].first); used.erase (that); } //Schutz wieder deaktivieren protectOff (); } //Gebundenen Mutex erzeugen Mutex (T &bind) : bound(&bind), boundActive(false) {} //Ungebundenen Mutex erzeugen, indem bound=0 gesetzt wird Mutex () : bound(0), boundActive (false) { unboundInitialize (); } //Mutexe (gebundene und ungebundene) destruieren ~Mutex () { if (isBound()) boundDelete (); else unboundDelete (); } //Folgende Funktion bindet einen Mutex zu einem neuen Objekt void bind (T &b) { if (isBound()) boundDelete (); else unboundDelete (); boundInitialize (b); } //Folgende 2 Funktionen aktivieren/deaktivieren gebundene oder ungebunde Mutexe void enable () { if (isBound()) boundActivate (); else unboundActivate (); } void disable () { if (isBound()) boundDeactivate (); else unboundDeactivate (); } //Helfer-Funktionen: //available gibt die Anzahl der freien Critical-Sections zurück, //bevor MutexPrivate::Pool::resize neue angelegt werden müssen. static int available () { protectOn(); int ret = MutexPrivate::Pool::getInstance().available(); protectOff(); return ret; } //resize verkleinert oder vergrößert den Pool static void resize (int x) { protectOn (); MutexPrivate::Pool::getInstance().resize(x); protectOff (); } }; //Speicher für die statischen Elementvariablen erzeugen template <typename T> std::map <T *, std::pair<CRITICAL_SECTION *, int> > Mutex<T>::used; template <typename T> CRITICAL_SECTION Mutex<T>::intern; template <typename T> bool Mutex<T>::init = false; } //namespace
[ Dieser Beitrag wurde am 15.12.2002 um 15:14 Uhr von Noesis editiert. ]