D
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. ]