In einem Thread habe ich mehrere aufeinanderfolgende Code-Zeilen, die umbedingt hintereinander abgearbeitet werden müssen (es darf kein anderer Thread dazwischen drankommen). Welche Möglichkeiten gibt es ???
es geht also darum, dass ein Codebereich nur von einem Thread ausgeführt wird. Threadwechsel müssen deshalb nicht unterdrückt werden, der andere Thread muss dann lediglich solange warten, bis der Bereich wieder freigegeben wird. Die geht mit Mutex. Zuerst ein Beispiel:
C/C++ Code:
1 2 3 4 5 6 7 8 9
1 2 3 4 5 6 7 8 9
//global
pthread_mutex_t code_access;
// irgendwo vor der Erzeugung des Threads/der Threads
pthread_mutex_init( &code_access, NULL ); // Variable initialisieren
// in einer Funktion:
pthread_mutex_lock( &code_access ); // Mutex sperren
... // kritischer Programmcode
pthread_mutex_unlock( &code_access ); // Mutex freigeben
C/C++ Code:
1 2 3 4 5 6 7 8 9
//global
pthread_mutex_t code_access;
// irgendwo vor der Erzeugung des Threads/der Threads
pthread_mutex_init( &code_access, NULL ); // Variable initialisieren
// in einer Funktion:
pthread_mutex_lock( &code_access ); // Mutex sperren
... // kritischer Programmcode
pthread_mutex_unlock( &code_access ); // Mutex freigeben
C/C++ Code:
1 2 3 4 5 6 7 8 9
//global
pthread_mutex_t code_access;
// irgendwo vor der Erzeugung des Threads/der Threads
pthread_mutex_init( &code_access, NULL ); // Variable initialisieren
// in einer Funktion:
pthread_mutex_lock( &code_access ); // Mutex sperren
... // kritischer Programmcode
pthread_mutex_unlock( &code_access ); // Mutex freigeben
Der Grund ist folgender, wenn ein Mutex unter Linux 'gelockt' ist, und ein 2. Thread will locken, muss der 2. solange warten, bis der Mutex wieder freigegeben wird. Dies geschieht in dem Beispiel erst nach dem Programmcode. Dadurch können beide Threads nicht denselben Programmcode durchlaufen.
Natürlich ist das nicht auf EIN Porgrammblock beschränkt, in Wirklichkeit kann man den Mutex auch an mehreren Stellen verwendet. Das ist deshalb sinnvoll, weil Zugriffe auf Variablen das Problem sind, und auf solche Variablen kann man von mehreren Stellen aus zugreifen.
Wenn man ein 'unlock' vergisst, kann dies zu heftigen Problemen führen - also am besten gleich nach dem 'lock' ein 'unlock' schreiben und das Programm dann zwischen den Zeilen einfügen.
Das war nur ein kleiner Crashkurs, falls es noch Fragen gibt - nur keine Hemmungen [img]images/smiles/icon_smile.gif[/img]
Nur ...
... das mit dem Mutex ist schon klar. Und das Problem ist genau der Zugriff auf eine Variable.
Diese globale Variable wird im Main-Thread und in anderen Threads als Index für ein Array (das ebenfalls global ist) benutzt.
Und in einem bestimmten Thread wird diese Variable kurzfristig verändert, wovon der Main-Thread und die anderen jedoch nichts "mitbekommen" sollen.
Also müßte ich die Code-Zeilen in diesem Thread zwischen ein mutex_lock und ein mutex_unlock einfügen und jedesmal wenn im Main-Thread oder einem anderen Thread diese Variable verwendet wird, ebenfalls ein mutex_lock davor und ein mutex_unlock danach einfügen.
Das Problem dabei ist, daß die Variable 100te mal im Main-Thread verwendet wird und ich den Aufwand, jedesmal ein mutex_lock und ein mutex_unlock einzufügen, vermeiden wollte.
Ja genau, dazu sind die Dinger da. Allerdings brauchst Du für einen statischen MUTEX nur
static pthread_mutex_t name = PTHREAD_MUTEX_INITIALIZER; global einfügen. Die Variante mit pthread_mutex_init ist für dynamische Zuweisungen oder Shared Memory.
@Archangel
So einfach geht das nicht. Du mußt in des Tat an jeder Stelle lock/unlock einfügen - viel Spaß ...
Das ist aber ein gutes Beispiel, warum globale Variablen wirklich schädlich sind. Das sie global sind, ist kein Problem, dass aber jeder darauf zugreifen kann, ist das echte Problem - denn jeder muss vorher lock/unlock aufrufen. Deshalb sollte man globale Variablen immer kapseln, in C++ kann man auch ein Objekt daraus machen. Und nur Zugriffsfunktionen dürfen diese Variablen ändern:
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12
1 2 3 4 5 6 7 8 9 10 11 12
int get_arrayvalue( size_t index ) {
lock();
int result = array[index];
unlock();
return result;
}
void set_arrayvalue( size_t index, int value ) {
lock();
array[index] = value;
unlock();
}
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12
int get_arrayvalue( size_t index ) {
lock();
int result = array[index];
unlock();
return result;
}
void set_arrayvalue( size_t index, int value ) {
lock();
array[index] = value;
unlock();
}
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12
int get_arrayvalue( size_t index ) {
lock();
int result = array[index];
unlock();
return result;
}
void set_arrayvalue( size_t index, int value ) {
lock();
array[index] = value;
unlock();
}
Natürlich benötigt man, je nach Art der globalen Variable, mehr und klomplexere Funktionen. Vielleicht kannst du dein Programm noch umstellen, sonst musst du wirklich jeden Zugriff in lock/unlock einschließen.
Auch wenn dir die Antwort/Lösung nicht gefällt, ich bin schuldlos [img]images/smiles/icon_wink.gif[/img]
Darf ich noch kurz darauf hinweisen, daß die Fuxsche Methode natürlich richtig ist, aber nicht ganz unproblematisch wenn innerhalb von lock und unlock eine Exception geworfen wird. Dann wird nämlich der lock nicht mehr freigegeben und das Programm bleibt irgendwann stehen (Deadlock).
Ich habe diesbezüglich unter Windows schon nette Erfahrungen gemacht. Wir haben dazu eine abstrakte Klasse erzeugt, die threadsicheren Zugriff an Klassen vererben kann. Ich übertrage das mal freihand auf Linux, wenn's irgendwo hakt merkt Ihr das schon:
class SingleLock
{
private:
Threadsafe* m_pThreadsafe;
public:
SingleLock(Threadsafe* threadsafe, bool lock = false)
{
m_pThreadsafe = threadsafe;
if (lock)
Lock();
}
~SingleLock()
{
Unlock();
}
void Lock()
{
if (m_pThreadsafe)
m_pThreadsafe->Lock();
}
void Unlock()
{
if (m_pThreadsafe)
m_pThreadsafe->Unlock();
}
};
Wollen wir nun folgende Klasse threadsicher machen:
C/C++ Code:
1 2 3 4 5 6 7 8
1 2 3 4 5 6 7 8
class Container
{
private:
int m_Value;
public:
void setVal(int value) {m_Value = value;};
int getVal() {return m_Value;};
};
C/C++ Code:
1 2 3 4 5 6 7 8
class Container
{
private:
int m_Value;
public:
void setVal(int value) {m_Value = value;};
int getVal() {return m_Value;};
};
C/C++ Code:
1 2 3 4 5 6 7 8
class Container
{
private:
int m_Value;
public:
void setVal(int value) {m_Value = value;};
int getVal() {return m_Value;};
};
Das wird zu
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
class Container : public Threadsafe
{
private:
int m_Value;
public:
void setVal(int value)
{
SingleLock slock(this); // entweder explizit locken
slock->Lock();
m_Value = value;
slock->Unlock();
};
int getVal()
{ // oder implizit
SingleLock slock(this, true); // ab hier ist gesperrt return m_Value;
// hier wird automatisch aufgeräumt
};
};
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
class Container : public Threadsafe
{
private:
int m_Value;
public:
void setVal(int value)
{
SingleLock slock(this); // entweder explizit locken
slock->Lock();
m_Value = value;
slock->Unlock();
};
int getVal()
{ // oder implizit
SingleLock slock(this, true); // ab hier ist gesperrt return m_Value;
// hier wird automatisch aufgeräumt
};
};
C/C++ Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
class Container : public Threadsafe
{
private:
int m_Value;
public:
void setVal(int value)
{
SingleLock slock(this); // entweder explizit locken
slock->Lock();
m_Value = value;
slock->Unlock();
};
int getVal()
{ // oder implizit
SingleLock slock(this, true); // ab hier ist gesperrt return m_Value;
// hier wird automatisch aufgeräumt
};
};
Dann kann das Teil Exceptions werfen wie es will, oder auch ein Rücksprung aus einer Funktion mit mehreren returns bringt keine Probleme. Sobald das Objekt gelöscht wird, ruft es automatisch Unlock auf, das wiederum Zugriff auf das mutex-Handle hat.
Aber man kann auch als Aufrufer explizit Lock/Unlock von Container aufrufen... das kann je nach Situation Sinn machen, daß die Funktionen intern locken, daß aber auch der Aufrufer das machen kann. Wer das nicht will, muß Lock/Unlock in Threadsafe private machen und SingleLock als friend definieren.
Das sollte so weitgehend richtig auch unter Linux funktionieren, ist fast analog zu Win, nur andere Handle/Funktionsnamen.
Falls es dafür schon eine fertige Klasse gibt... nun gut, dann war's halt zur Übung. Das Ding läßt sich auch noch etwas verfeinern um zweimaliges Unlock zu verhindern etc. Geht mehr um die Idee. [img]images/smiles/icon_wink.gif[/img]
Ach so, noch ein Wort: dies setzt vom Klassendesign voraus, daß man immer nur auf Klassenebene die gesamte Klasse locken kann, nicht jede Variable einzeln. Aber dies hat sich bei uns für die Praxis als absolut ausreichend und auch vom Design her korrekt erwiesen.
Bei Paaren wie lock/unlock, sehe ich natürlich immer das Exceptionproblem - aber ich ging hier erstmal von C aus, also hab ich nichts dazu gesagt - im C++-Forum wäre es was anderes. Abzuleiten halte ich für keine gute Idee (ich habs selbst mal mit einem template gemacht). Das wollt ich auch nicht diskutieren. Vor ein paar Tagen hab ich einen Artikel von Stroustrup gelesen, und der hat meine Template-Variante noch erweitert und verbessert. In http://www.research.att.com/~bs/papers.html unter "Wrapping C++ Member Functions Call" ist eine PDF-Datei.
Mit einem etwas vereinfachten Syntax würde man sowas schreiben:
C/C++ Code:
Wrapper<Container> container( new Container ); // Wrapper wrappt die Threadsicherheit
container->setValue( 5 ); // vor dem Aufruf von Container::setValue wird automatisch gelockt, und danach entlockt (vor dem Semikolon)
C/C++ Code:
Wrapper<Container> container( new Container ); // Wrapper wrappt die Threadsicherheit
container->setValue( 5 ); // vor dem Aufruf von Container::setValue wird automatisch gelockt, und danach entlockt (vor dem Semikolon)
C/C++ Code:
Wrapper<Container> container( new Container ); // Wrapper wrappt die Threadsicherheit
container->setValue( 5 ); // vor dem Aufruf von Container::setValue wird automatisch gelockt, und danach entlockt (vor dem Semikolon)
In Wirklichkeit muss man dem Wrapper noch sagen, wie gelockt wird.
Das mit den Deadlocks ist natürlich richtig, allerdings ist das mit Euren Exceptions interessant. Was versteht Ihr denn da so genau unter Linux? Wenn hier ein einzelner User-Level Thread abschmiert, sind eh alle anderen weg.
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.