Thread safe Grundsatzfragen

Thomasio

Erfahrenes Mitglied
Nachdem ich gerade den Fehler in meinem Programm gefunden habe
( http://www.tutorials.de/c-c/368383-wie-finde-ich-den-fehler.html )
stosse ich hier auf ein viel grundsätzlicheres Problem, mit Namen "thread-safe".

Ich nutze im Programm ein paar wenige aber doch ein paar Funktionen, die nicht thread safe sind, wie z.B. memset().
Das ist kein Problem, solange ich das weiss, Mutex drumrum, fertig.
Aber woher weiss ich, was thread safe ist und was nicht?
Im Web finden sich zu 1000den Artikel, teilweise 5-10 Jahre alt, die besagen, dass eigentlich überhaupt nichts thread safe ist, nicht mal die string Klasse.
In neueren Artikeln findet man dann Hinweise, dass string jetzt doch thread safe ist, aber nicht auf jeder Plattform, teilweise sogar noch abhängig vom verwendeten Compiler.
Genauso gibt es 1000 Artikel zu alloc() und malloc() und so gut wie allen anderen C Funktionen.

In kurz, ich sehe den Wald vor lauter Bäumen nicht mehr.

Klar, wenn 2 threads sich den selben string teilen müssen, dann muss ich den Zugriff sichern, via Mutex oder so, aber wie schaut das bei internen Funktionen der Klassen aus?

Wenn ich mache:

Code:
string MyString = "abcde";

dann muss die string Klasse dafür den Speicher reservieren und die verwendet vermutlich auch nur malloc() oder ähnliches.
Wenn also 2 oder mehr threads gleichzeitig versuchen Speicher für einen string (oder sonstwas) zu reservieren, wobei die strings in beiden threads jeweils privat sind, wann ist das thread safe?

Wenn ich mache:

Code:
string MyString = "abcde";
string MyString2 = "fghij";
MyString = MyString + MyString2;

dann muss die string Klasse zuerst 2 Mal Speicher reservieren und dann den ersten Speicher erweitern.
Was passiert, wenn 2 oder mehr threads das gleichzeitig machen, mit jeweils privaten strings.

Bevor wir uns aber jetzt auf die string Klasse versteifen, in meinem Problem aus dem anderen Beitrag taucht das noch viel grundsätzlicher auf.
Da hatte ich 2 threads, von denen einer eine einfache Schleife laufen hatte

Code:
for(UINT i = 0; i < 1000; i++)
...

Im anderen thread gab es

Code:
memset(...);

Wenn nun zufällig beides gleichzeitig lief (das kam alle paar Stunden mal vor) dann hat memset() die Initialisierung vom Schleifenzähler überschrieben, ich bekam also beim ersten Lauf der Schleife nicht i = 0 sondern i = undefiniert, und logisch stürzt das Ganze bei i++ ab.
Da stellt sich mir die Frage, kann ich in multithreaded Programmen nicht mal mehr private Variablen initialisieren ohne Mutex drumrum?
 
Zuletzt bearbeitet:
Du verwechselst da threadsafe mit reentrant (heißt das so?).

Angenommen, Thread a und Thread b rufen beide malloc auf.
Weiters angenommen, die Implementierung von malloc hat am Anfang eine Variable, zB
void *memory;
Auf diesen Pointer wird dann irgendwie über das Betriebssystem Speicher reserviert und dann mit return der Pointer zurückgegeben.

Dann hat der malloc-Aufruf von Thread a eine Variable memory und der malloc-Aufruf von Thread b seine eigene Variable memory.
Zwei verschiedene Variablen. Kein Grund, auf irgendeine Threadsafeheit aufzupassen.

Das ist auch mit "reentrant" gemeint: Eine reentrante Funktion kann problemlos mehrmals gleichzeitig aufgerufen werden.

Ein Beispiel für eine Funktion, die das Kriterium nicht erfüllt, wäre, wenn sie auf eine globale Variable zugreift.
Dann würden alle Durchgänge der Funktion auf ein und dieselbe Variable zugreifen->Massnahmen zur Threadsicherheit sind nötig.

Also solange du bestimmte Variablen nur in einem Thread verwendest, brauchst du nichts absichern
Auch wenn du Funktionen verwendest, die der andere Thread auch braucht, solange die nicht Variablen vom anderen Thread verwenden.

Gruß
 
Danke für die Antwort.
Ich hatte mich vor dem Beitrag hier quer durchs halbe Web gelesen, und so in etwa wie du das jetzt sagst hatte ich das auch verstanden.

Was mich nur richtig gewundert hat, war der Crash in meiner Anwendung, durch den ich überhaupt erst darauf aufmerksam geworden bin, dass memset() nicht thread safe ist.
Da habe ich dank DrWatson den Fehler entdeckt, und der zeigte mir, dass der Absturz durch den undefinierten Schleifenzähler ausgelöst wurde.

Allerdings habe ich mittlerweile rausgefunden, dass ich da noch mehr nicht thread safe Zeug drin hatte, und nachdem ich das alles in einen mutex gepackt habe, läuft es jetzt anscheinend sauber.
Was mich zurück bringt zur Haupt-Frage:
Wenn ich irgendwelche library Funktionen verwende, ala atoi(), memcpy(), vector.push_back() und was weiss ich sonst noch, wie finde ich heraus ob das thread safe ist, ohne jedes Mal Google zu bemühen und mich durch ein Dutzend Foren zu lesen?
 
Nocheinmal: Was soll an diesen Funktionen denn threadsafe gemacht werden?

atoi bekommt einen Pointer auf ein char-Array (um das du dich zuerst selber kümmern musst) und gibt ein int zurück.

Die Variablen, die atoi selber in sich drinnen anlegt, legt jeder Durchgang von atoi für sich selber an und lässt keinen anderen ran.
Um das Aufräumen dieser Variablen kümmert sich atoi auch wieder selber.

Zurückgegeben wird ein int-Wert, den du in einer eigenen Variable speichern kannst.
Die Variable musst auch du selber bereitstellen.

Die einzigen Probleme könnten sich ergeben, wenn:

1) Du während einem atoi von einem anderen Thread aus am char-Array was änderst.
Hier müsstest du dich selber um den Mutex etc kümmern, welcher Thread Zugriff auf das Array hat.
Denn woher soll atoi denn wissen, dass es andere Threads gibt die das Array brauchen oder sogar noch einen Mutex dafür bereit haben? Eben garnicht.

2) Wenn atoi fertig ist und der Returnwert in ein int geschrieben wird, und ein anderer Thread gleichzeitig einen anderen Wert in das int schreibt.
Der Wert, der dann am Schluss drinnensteht, ist Mist.
Wenn die Variable für den Returnwert also von mehreren Threads benutzt wird, auch absichern

Für memcpy gilt das Gleiche: Wenn die Übergabewerte von anderen Threads während eines Aufrufs bearbeitet werden könnten, entsprechend absichern. Auch die Variable für den Returnwert.
Was dann im memcpy passiert, ist unwichtig.

Bei Methoden von vector wird allerdings bei jedem Aufruf der gleiche vector gebraucht, hier vorsichtshalber immer absichern.
Der vector ist ja letztendlich eine Variable, der dann von mehreren Threads verwendet wird...

Im Zweifelsfall schadet es auch nicht, mehr abzusichern oder die Doku zu befragen...
Oft ist die Situation aber einfach logisch

Falls du Einsicht in den Quellcode hast, Faustregel:
Wenn Variablen verwendet werden, die static sind oder irgendwo außerhalb der Funktion angelegt werden (und nicht schon ein Mutex etc. verwendet wird), ist es nicht threadsafe.
 
Das Problem ist, soweit ich das verstanden habe, wenn die aufgerufene Funktion irgendwas global speichert, wie z.B. strtok(), der speichert den Reststring irgendwo global, rufen also 2 threads gleichzeitig strtok() auf, gibt das Murks, darum gibt es auch strtok_r(), wo das besser ist, so habe ich es zumindest irgendwo gelesen.

Daraus schliesse ich messerscharf, wenn ich eine Funktion aus irgendeiner Library verwenden will, muss ich immer vorher wissen, ob diese Funktion thread safe ist, oder ich müsste die library durchforsten, bzw. mich quer durch Google lesen um eine Info dazu zu bekommen.
Woher soll ich z.B. wissen, ob atoi() beim konvertieren nichts globales zwischendrin speichert? Die Funktion hat ja schon existiert, als noch keine Mensch wusste, was multithreaded überhaupt ist.
 
Mit strtok hast du recht, sehr unschön von den MS-Programmierern...
http://research.microsoft.com/en-us/um/redmond/projects/invisible/src/crt/strtok.c.htm
lasttoken ist static (also quasi global, aber nur in strtok verwendbar)
Manchmal hilft es die richtige Dokumentation zu lesen ;-] Nicht alles was von Microsoft kommt, bezieht sich automatisch auf das Desktop-Windows. Project Invisible zielt z.B. auf eingebettete Systeme ab. Bei der Standardbibliothek-Implementierung für Desktop-Rechner sieht es anders aus:
http://msdn.microsoft.com/en-us/library/2c8d19sb(v=VS.100).aspx hat gesagt.:
Each function [strtok, _strtok_l, wcstok, _wcstok_l, _mbstok, _mbstok_l] uses a thread-local static variable for parsing the string into tokens. Therefore, multiple threads can simultaneously call these functions without undesirable effects.

Im Allgemeinen gilt: der C-Standard garantiert keinerlei Eigenschaften bezüglich Threadsicherheit. Ob eine Funktion in einer bestimmten Implementierung reentrant ist, musst du dementsprechend in der (richtigen ;)) Dokumentation nachlesen.

Grüße,
Matthias
 
Danke für die Korrektur. Ich sollte wohl nicht ohne zu lesen das erste Googleergebnis anklicken und nur den Code anzuschauen...
 
Ich danke euch für die Antworten, alles nicht so einfach wie ich es gerne hätte, aber da muss ich wohl durch.
Wenn ich denn lange genug lebe, werde ich mir das wohl irgendwann auch merken können, ohne es jedes Mal nachlesen zu müssen.
 
Zurück