Kürzlich stellte ich eine Frage mit dem Titel "Ist Malloc-Thread sicher?" und darin fragte ich: "Ist Malloc wieder am Start?"
Ich hatte den Eindruck, dass alle Wiedereinsteiger threadsicher sind.
Ist diese Annahme falsch?
Kürzlich stellte ich eine Frage mit dem Titel "Ist Malloc-Thread sicher?" und darin fragte ich: "Ist Malloc wieder am Start?"
Ich hatte den Eindruck, dass alle Wiedereinsteiger threadsicher sind.
Ist diese Annahme falsch?
Antworten:
Wiedereintretende Funktionen basieren nicht auf globalen Variablen, die in den C-Bibliotheksheadern verfügbar gemacht werden. Nehmen Sie zum Beispiel strtok () vs strtok_r () in C.
Einige Funktionen benötigen einen Platz zum Speichern eines "Work in Progress". Mit neu eingegebenen Funktionen können Sie diesen Zeiger im eigenen Speicher des Threads und nicht in einem globalen Speicher angeben. Da dieser Speicher nur für die aufrufende Funktion verfügbar ist, kann er unterbrochen und erneut eingegeben werden (Wiedereintritt). Da in den meisten Fällen ein gegenseitiger Ausschluss über das hinaus, was die Funktion implementiert, nicht erforderlich ist, wird dies häufig als solche angesehen fadensicher . Dies ist jedoch per Definition nicht garantiert.
errno ist jedoch ein etwas anderer Fall auf POSIX-Systemen (und ist in jeder Erklärung, wie dies alles funktioniert, der seltsame Punkt) :)
Kurz gesagt bedeutet Wiedereintritt häufig Thread-sicher (wie in "Verwenden Sie die Wiedereintrittsversion dieser Funktion, wenn Sie Threads verwenden"), aber Thread-sicher bedeutet nicht immer Wiedereintritt (oder umgekehrt). Wenn Sie sich mit Thread-Sicherheit und Parallelität befassen , müssen Sie über nachdenken. Wenn Sie ein Mittel zum Sperren und gegenseitigen Ausschließen bereitstellen müssen, um eine Funktion zu verwenden, ist die Funktion nicht von Natur aus threadsicher.
Es müssen jedoch auch nicht alle Funktionen untersucht werden. malloc()
muss nicht wiedereintrittsfähig sein, es hängt nicht von irgendetwas ab, das außerhalb des Bereichs des Einstiegspunkts für einen bestimmten Thread liegt (und ist selbst threadsicher).
Funktionen, die statisch zugewiesene Werte zurückgeben, sind ohne die Verwendung eines Mutex, Futex oder eines anderen atomaren Sperrmechanismus nicht threadsicher. Sie müssen jedoch nicht wieder eintreten, wenn sie nicht unterbrochen werden sollen.
dh:
static char *foo(unsigned int flags)
{
static char ret[2] = { 0 };
if (flags & FOO_BAR)
ret[0] = 'c';
else if (flags & BAR_FOO)
ret[0] = 'd';
else
ret[0] = 'e';
ret[1] = 'A';
return ret;
}
Wie Sie sehen können, wäre es eine Katastrophe, wenn mehrere Threads dies ohne irgendeine Art von Sperre verwenden. Es hat jedoch keinen Zweck, erneut einzutreten. Sie werden darauf stoßen, wenn dynamisch zugewiesener Speicher auf einer eingebetteten Plattform tabu ist.
Bei der rein funktionalen Programmierung bedeutet Wiedereintritt oft keine Thread-Sicherheit, sondern hängt vom Verhalten definierter oder anonymer Funktionen ab, die an den Funktionseintrittspunkt, die Rekursion usw. übergeben werden.
Eine bessere Möglichkeit, "threadsicher" zu machen, ist der gleichzeitige Zugriff , was die Notwendigkeit besser verdeutlicht.
TL; DR: Eine Funktion kann wiedereintrittsfähig, threadsicher sein, beides oder keines.
Die Wikipedia-Artikel zu Thread-Sicherheit und Wiedereintritt sind lesenswert. Hier einige Zitate:
Eine Funktion ist threadsicher, wenn:
Es manipuliert nur gemeinsam genutzte Datenstrukturen auf eine Weise, die eine sichere Ausführung durch mehrere Threads gleichzeitig garantiert.
Eine Funktion ist wiedereintrittsfähig, wenn:
Es kann zu jedem Zeitpunkt während seiner Ausführung unterbrochen und dann sicher erneut aufgerufen ("neu eingegeben") werden, bevor seine vorherigen Aufrufe die Ausführung abschließen.
Als Beispiele für einen möglichen Wiedereintritt gibt die Wikipedia das Beispiel einer Funktion an, die von Systeminterrupts aufgerufen werden soll: Angenommen, sie wird bereits ausgeführt, wenn ein anderer Interrupt auftritt. Aber denken Sie nicht, dass Sie sicher sind, nur weil Sie nicht mit Systeminterrupts codieren: Sie können Wiedereintrittsprobleme in einem Single-Thread-Programm haben, wenn Sie Rückrufe oder rekursive Funktionen verwenden.
Der Schlüssel zur Vermeidung von Verwirrung besteht darin, dass sich der Wiedereintritt nur auf einen ausgeführten Thread bezieht. Es ist ein Konzept aus der Zeit, als es keine Multitasking-Betriebssysteme gab.
Beispiele
(Leicht modifiziert aus den Wikipedia-Artikeln)
Beispiel 1: nicht threadsicher, nicht wiedereintrittsfähig
/* As this function uses a non-const global variable without
any precaution, it is neither reentrant nor thread-safe. */
int t;
void swap(int *x, int *y)
{
t = *x;
*x = *y;
*y = t;
}
Beispiel 2: Thread-sicher, nicht wiedereintrittsfähig
/* We use a thread local variable: the function is now
thread-safe but still not reentrant (within the
same thread). */
__thread int t;
void swap(int *x, int *y)
{
t = *x;
*x = *y;
*y = t;
}
Beispiel 3: nicht threadsicher, wiedereintrittsfähig
/* We save the global state in a local variable and we restore
it at the end of the function. The function is now reentrant
but it is not thread safe. */
int t;
void swap(int *x, int *y)
{
int s;
s = t;
t = *x;
*x = *y;
*y = t;
t = s;
}
Beispiel 4: Gewindesicher, wiedereintrittsfähig
/* We use a local variable: the function is now
thread-safe and reentrant, we have ascended to
higher plane of existence. */
void swap(int *x, int *y)
{
int t;
t = *x;
*x = *y;
*y = t;
}
t = *x
, anruft swap()
, t
wird er überschrieben, was zu unerwarteten Ergebnissen führt.
Das hängt von der Definition ab. Zum Beispiel verwendet Qt Folgendes:
Eine thread-sichere * Funktion kann gleichzeitig von mehreren Threads aufgerufen werden, selbst wenn die Aufrufe gemeinsam genutzte Daten verwenden, da alle Verweise auf die gemeinsam genutzten Daten serialisiert werden.
Eine Wiedereintrittsfunktion kann auch gleichzeitig von mehreren Threads aufgerufen werden, jedoch nur, wenn jeder Aufruf seine eigenen Daten verwendet.
Daher ist eine thread-sichere Funktion immer wiedereintrittsfähig, aber eine wiedereintrittsfähige Funktion ist nicht immer threadsicher.
Als Erweiterung wird eine Klasse als wiedereintrittsfähig bezeichnet, wenn ihre Mitgliedsfunktionen sicher von mehreren Threads aufgerufen werden können, solange jeder Thread eine andere Instanz der Klasse verwendet. Die Klasse ist threadsicher, wenn ihre Mitgliedsfunktionen sicher von mehreren Threads aufgerufen werden können, selbst wenn alle Threads dieselbe Instanz der Klasse verwenden.
aber sie warnen auch:
Hinweis: Die Terminologie in der Multithreading-Domäne ist nicht vollständig standardisiert. POSIX verwendet Definitionen für Wiedereintritt und Thread-Sicherheit, die sich für seine C-APIs etwas unterscheiden. Stellen Sie bei Verwendung anderer objektorientierter C ++ - Klassenbibliotheken mit Qt sicher, dass die Definitionen verstanden werden.