ich greife von mehreren Threads auf eine gemeinsame Ressource zu. Diese sieht ungefähr wie folgt aus:
C/C++ Code:
struct
{
int id;
bool locked;
// [...]
};
C/C++ Code:
struct
{
int id;
bool locked;
// [...]
};
C/C++ Code:
struct
{
int id;
bool locked;
// [...]
};
Die Threads suchen eine bestimme id. Werden sie fündig, so sollen sie locked auf true setzen. Hat ein Thread ein solches Objekt gelockt, so kann er die id ggf. verändern (könnte problematisch sein).
Das Problem ist folgendes: Hat ein Thread seine gesuchte id gefunden, kann aber nicht auf das Objekt zugreifen, da locked == true, so soll er warten bis locked == false ist. Wie gesagt kann aber rein theoretisch die id zunächst die korrekte sein, locked == true und "plötzlich" die id sich verändern ...
Ich brauche jedenfalls einen Weg um die gemeinsamen Ressourcen (wie die Variable "locked") zu schützen.
for( int i = 0; i < LOOP_COUNT; ++i ) {
printf("thread(%d/%d) output: %.8d\n", *id, iter, count++);
}
InterlockedExchange(&lock, 0);
return 0;
}
Hier nutze ich die globale Variable lock um den Bereich in dem count verändert und die Konsoleausgabe stattfindet zu schützen.
Mit dem Sleep(1) innerhalb der while-Schleife habe ich auch eine relativ gute Performance (Warum ist dies bei Sleep(0) nicht so? Bzw. welche Rolle übernimmt Sleep() hier eigentlich?).
Eine Critical Section scheint mir jedoch performanter zu sein:
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12 13
1 2 3 4 5 6 7 8 9 10 11 12 13
unsigned __stdcall ThreadFunc(void* ptr)
{
long* id = static_cast<long*>(ptr);
int iter = 0;
EnterCriticalSection(&cs);
for( int i = 0; i < LOOP_COUNT; ++i ) {
printf("thread(%d/%d) output: %.8d\n", *id, iter, count++);
}
LeaveCriticalSection(&cs);
return 0;
}
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12 13
unsigned __stdcall ThreadFunc(void* ptr)
{
long* id = static_cast<long*>(ptr);
int iter = 0;
EnterCriticalSection(&cs);
for( int i = 0; i < LOOP_COUNT; ++i ) {
printf("thread(%d/%d) output: %.8d\n", *id, iter, count++);
}
LeaveCriticalSection(&cs);
return 0;
}
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12 13
unsigned __stdcall ThreadFunc(void* ptr)
{
long* id = static_cast<long*>(ptr);
int iter = 0;
EnterCriticalSection(&cs);
for( int i = 0; i < LOOP_COUNT; ++i ) {
printf("thread(%d/%d) output: %.8d\n", *id, iter, count++);
}
LeaveCriticalSection(&cs);
return 0;
}
Auch ein Mutex Object ist denkbar:
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12 13
1 2 3 4 5 6 7 8 9 10 11 12 13
unsigned __stdcall ThreadFunc(void* ptr)
{
long* id = static_cast<long*>(ptr);
int iter = 0;
WaitForSingleObject(hMutex, INFINITE);
for( int i = 0; i < LOOP_COUNT; ++i ) {
printf("thread(%d/%d) output: %.8d\n", *id, iter, count++);
}
ReleaseMutex(hMutex);
return 0;
}
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12 13
unsigned __stdcall ThreadFunc(void* ptr)
{
long* id = static_cast<long*>(ptr);
int iter = 0;
WaitForSingleObject(hMutex, INFINITE);
for( int i = 0; i < LOOP_COUNT; ++i ) {
printf("thread(%d/%d) output: %.8d\n", *id, iter, count++);
}
ReleaseMutex(hMutex);
return 0;
}
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12 13
unsigned __stdcall ThreadFunc(void* ptr)
{
long* id = static_cast<long*>(ptr);
int iter = 0;
WaitForSingleObject(hMutex, INFINITE);
for( int i = 0; i < LOOP_COUNT; ++i ) {
printf("thread(%d/%d) output: %.8d\n", *id, iter, count++);
}
ReleaseMutex(hMutex);
return 0;
}
Bekanntlich führen viele Wege nach Rom. Dennoch hat man die unterschiedlichen Funktionen sicherlich nicht grundlos implementiert und es gibt sicherlich die ein oder anderen Vor- u. Nachteile.
Mein Problem ist, dass mir trotz intensiver Studie der MSDN es schwer fällt, die einzelnen Herangehensweisen zu unterscheiden.
Die Interlocked Zugriffe auf Variablen sind sicherlich vorrangig zum Vollzug atomarer Operationen durch die fence-Instruktionen.
Kann mir jemand weiterhelfen?
Zuletzt bearbeitet von FrEEzE2046 am 00:17:59 31.07.2010, insgesamt 1-mal bearbeitet
Die Interlocked Funktionen sind CPU spezifische Operationen welche das sichere Inkrementieren/Dekrementieren/Setzen/Lesen/etc. einer Variable ermöglichen. Also wenn du eine shared Variable x hast, dann kannst du über die Interlocked Funktionen (und nur über diese) sicher auf diese Variable zugreifen.
Der Vorteil liegt hierbei eindeutig in der Geschwindigkeit, die Interlocked Funktionen sind (wegen der ziemlichen Hardwarenähe) eindeutig die schnellste Synchronisationsart, jedoch nicht für alle Fälle sinnvoll.
Die Critical Section ist praktisch fast das selbe wie ein Mutex nur etwas schneller und auf einen Prozess begrenzt. Critical Sections sind empfehlenswert wenn die kritische Codestelle nur selten von anderen Threads genutzt wird, also in der Regel nur selten auf die "Freigabe" eines anderen Threads gewartet werden muss. Critical Sections sind sehr schnell wenn es darum geht nur zu überprüfen ob eine Codestelle gelocked ist oder nicht, und daher im oben genannten Fällen empfehlenswert. Falls die Codestelle momentan von einem anderen Thread "benutzt" wird, wird zuerst eine gewisse Anzahl CPU-Zyklen auf "Volllast", d.h. aktiv gewartet (die Anzahl der Zyklen lässt sich über den Spin-Count festlegen). Anschließend wird ein normaler Mutex alloziert und benutzt, was das ganze dann schon bedeutend langsamer werden lässt. Critical Section sollten für die meisten Inter-Thread Synchronisations-Szenarien ausreichend sein. Bei besonders zeitkritischen Anwendungen kann man den Spin-Count ensprechend hoch einstellen, natürlich auf Kosten der CPU-Auslastung, da ja aktiv (aus Programmierersicht könnte man sagen in einer Endlosschleife) gewartet wird.
Das dritte Objekt im Bunde ist der Mutex, der langsame Rundumschlag Microsofts. Mutex unterstützen sowohl Inter-Thread Synchronisation als auch Inter-Process Synchronisation, erfüllen aber im Grunde das selbe wie die Critical Section. Ein entscheidender Nachteil ist die Geschwindigkeit, da zum Einen bei jeder Abfrage des Mutex-Status in den Kernel-Mode geschaltet wird, und zum Anderen nicht "aktiv" auf ein Mutex-Objekt gewartet wird. Das Betriebssystem (bzw. dessen Thread-Scheduler) entscheidet also wann wieder zu einem "wartenden" Mutex zurückgesprungen wird.
Hm, doch keine so kurze Zusammenfassung, daher hier nochmal ne richtig kurze:
Interlocked: Sehr sehr schnell, aber sehr speziell
Critical Section: Objekt der Wahl für Inter-Thread Synchronisation innerhalb eines Prozesses.
Mutex: Rundumschlag, gut für Inter-Prozess Synchronisation.
Die Interlocked-Funktionen sind flexibler. Damit kannst du z.B. auch lockfreie Datenstrukturen implementieren. Also Datenstrukturen die ganz ohne extra "Mutex" oder CRITICAL_SECTION auskommen.
Ich würde empfehlen da grundsätzlich die Finger davon zu lassen, ausser du hast a) sehr spezielle Anforderungen und b) kennst dich schon sehr gut mit dem Thema aus.
CRITICAL_SECTION ist die "schnelle" Mutex-Variante von Windows. Kann man eigentlich überall verwenden, es sei denn man braucht ein Feature welches CRITICAL_SECTION nicht bietet. Das könnte sein
* Prozessübergreifend verwendbar (CRITICAL_SECTION ist nur innerhalb eines einzigen Prozesses verwendbar)
* Timed-Wait Funktion (CRITICAL_SECTION bietet nur TryEnter)
* Deterministisches Scheduling (z.B. FIFO, Priority)
Eine Windows Kernel Mutex schliesslich bietet die ersten beiden Features. Dafür sind die Dinger verdammt viel langsamer als CRITICAL_SECTIONs.
Meine Empfehlung wäre die erste mögliche Option aus dieser Liste zu verwenden:
1) Libraries ala Boost.Thread verwenden die das ganze abstrahieren/kapseln
2) CRITICAL_SECTION
3) Windows Kernel Mutex
4) Nur wenn's gar nicht anders geht Interlocked-Funktionen
1) Libraries ala Boost.Thread verwenden die das ganze abstrahieren/kapseln
Ich wüsste nicht was die Boost Library anders machen sollte außer Critical Sections zu verwenden. Und wenn du unbedingt ne Objektorientierte Kapselung verwenden willst dann bitte:
Header:
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#pragma once
#include <Windows.h>
//CThreadMutex is a wrapper for the Windows Mutex Synchronization Object
class CThreadMutex
{
public:
CThreadMutex(void);
~CThreadMutex(void);
void SetOrWait( void );
void Release( void );
private:
CRITICAL_SECTION m_Crit;
};
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#pragma once
#include <Windows.h>
//CThreadMutex is a wrapper for the Windows Mutex Synchronization Object
class CThreadMutex
{
public:
CThreadMutex(void);
~CThreadMutex(void);
void SetOrWait( void );
void Release( void );
private:
CRITICAL_SECTION m_Crit;
};
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
#pragma once
#include <Windows.h>
//CThreadMutex is a wrapper for the Windows Mutex Synchronization Object
class CThreadMutex
{
public:
CThreadMutex(void);
~CThreadMutex(void);
Heißt leider noch CThreadMutex weil ich die Klasse zuerst für Mutexes genutzt habe und anschließend auf Critical Sections umgeschrieben habe, ich wollte aber keine weitreichenden Änderungen im Code meines Programms machen.
Was ist mit den Interlocked-Funktionen? Sehr speziell? Nur, wenn's gar nicht anders geht?
Gibt es da Probleme? Ich nutze die überall, wo ich eine Variable synchronisieren muss.
Was ist mit den Interlocked-Funktionen? Sehr speziell? Nur, wenn's gar nicht anders geht?
Gibt es da Probleme? Ich nutze die überall, wo ich eine Variable synchronisieren muss.
Speziell in dem Sinne dass man damit IMHO nur einzelne Variablen synchronisieren kann, und keine ganzen Codeabschnitte. Die Meinung dass man sie nur benutzen soll wenns gar nicht anders geht teile ich nicht, schließlich sind die eigentlich relativ einfach zu benutzen.
Die Meinung dass man sie nur benutzen soll wenns gar nicht anders geht teile ich nicht, schließlich sind die eigentlich relativ einfach zu benutzen.
Naja, ich versuche immer das schnellste zu benutzen. Interlocked ist da schon recht weit vorne. Aber nicht vergessen man darf, daß die CritSect schon schneller als nur zwei Interlockeds ist.
Wieso sollte man sich immer alles selbst programmieren?
Etwas selbst zu machen, nur weil es geht, ist meist schlecht. Wenn man es macht um dabei zu lernen, OK. Wenn man es nur macht weil man einen furchtbaren Dickschädel hat, ist es aber fast immer ein Fehler.
* Die Boost.Thread ist quasi-Standard, und wird fast 1:1 in der Form wohl auch im neuen C++ Standard landen
* Die Boost.Thread ist "tried and true", und von Leuten programmiert die ziemlich gut wissen was sie tun
* Die Boost.Thread ist komfortabel zu handhaben, was ich von deiner Klasse nicht sagen würde. Es fehlt z.B. die "Scoped-Lock" Klasse, die die Mutex/CRITICAL_SECTION im Konstruktor lockt und im Destruktor unlockt
Und noch ganz konkret zum Thema warum ich es für eine schlechte Idee halte, sich selbst was zu basteln, was es schon (als allgemein akzeptierte Komponente) fertig gibt...
Die meisten Leute machen bei der Implementierung nicht nur Fehler, sondern gewöhnen sich dabei auch an, bestimmte Dinge nach ihrem eigenen Dickschädel zu machen, und nicht wie sie "alle anderen" machen. Reicht schon wenn etwas was überall "foo" heisst bei dir dann "bar" heisst. Der Effekt der Implementierungsfehler dürfte klar sein. Der Effekt von anderen Namen/Begriffen ist, dass man anfängt ein anderes Vokabular zu verwenden. Und das ist IMO immer ein Nachteil, da es eine unnötige Kommunikations-Barriere darstellt.
Konkret am Beispiel deines Codes:
Implementierungs-Fehler:
* Deine CThreadMutex Klasse ist copy-constructable, dürfte es aber nicht sein
* Deine CThreadMutex Klasse ist assignable, dürfte es aber nicht sein
Quality-Of-Implementation:
* Der Konstruktor verwendet InitializeCriticalSection. InitializeCriticalSection kann fehlschlagen, diesen Umstand aber nicht kommunizieren. -> Lieber InitializeCriticalSectionAndSpinCount verwenden
* Du bietest keine Scoped-Lock Klasse an
* Du bietest keine Try-Lock Funktion an
Namen:
* "CThreadMutex" ist ein seltsamer name (was soll das "Thread" im Namen?). Entweder CriticalSection, wenn man einen Windows-spezifischen Namen will, oder einfach Mutex/RecursiveMutex/RecursiveTryMutex.
* "SetOrWait" ist ein Name den ich *noch nie* irgendwo in dem Zusammenhang gelesen/gehört habe. (Und für mich persönlich auch kontraintuitiv - "set" assoziiere ich im Zusammenhang mit Threads mit Events). Üblich wären "Lock"/"Unlock", "Acquire"/"Release" und "Enter"/"Leave". (Manchmal auch "Wait", finde ich persönlich aber nicht gut. Allerdings immer noch besser als SetOrWait")
Nächstes Thema anzeigen Vorheriges Thema anzeigen
Sie können keine Beiträge in dieses Forum schreiben. Sie können auf Beiträge in diesem Forum nicht antworten. Sie können Ihre Beiträge in diesem Forum nicht bearbeiten. Sie können Ihre Beiträge in diesem Forum nicht löschen. Sie können an Umfragen in diesem Forum nicht mitmachen.
c++.de ist Teilnehmer des Partnerprogramms von Amazon Europe S.à.r.l. und Partner des Werbeprogramms, das zur Bereitstellung eines Mediums
für Websites konzipiert wurde, mittels dessen durch die Platzierung von Werbeanzeigen und Links zu amazon.de
Werbekostenerstattung verdient werden kann.
Die Vervielfältigung der auf den Seiten www.c-plusplus.de, www.c-plusplus.info, www.c-sar.de, www.c-plusplus.net und www.baeckmann.de
enthaltenen Informationen ohne eine schriftliche Genehmigung des Seitenbetreibers ist untersagt
(vgl. §4 Urheberrechtsgesetz). Die Nutzung und Änderung der vorgestellten Strukturen und Verfahren in
privaten und kommerziellen Softwareanwendungen ist ausdrücklich erlaubt, soweit keine Rechte Dritter verletzt werden.
Der Seitenbetreiber übernimmt keine Gewähr für die Funktion einzelner Beiträge oder Programmfragmente, insbesondere
übernimmt er keine Haftung für eventuelle aus dem Gebrauch entstehenden Folgeschäden.