Threadsafe gegen Wiedereinsteiger

87

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?

Alphaneo
quelle

Antworten:

42

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.

Tim Post
quelle
2
Wiedereintritt bedeutet nicht, dass das Gewinde sicher ist. Reine Funktionen bedeuten Gewindesicherheit.
Julio Guerra
Tolle Antwort Tim. Um dies zu verdeutlichen, ist mein Verständnis von Ihrem "oft", dass Thread-Safe nicht Wiedereintritt bedeutet, aber auch Wiedereintritt bedeutet nicht Thread-sicher. Könnten Sie ein Beispiel für eine Wiedereintrittsfunktion finden, die nicht threadsicher ist?
Riccardo
@ Tim Post "Kurz gesagt, Wiedereintritt bedeutet oft Thread-sicher (wie in" Verwenden Sie die Wiedereintrittsversion dieser Funktion, wenn Sie Threads verwenden "), aber Thread-sicher bedeutet nicht immer Wiedereintritt." qt sagt das Gegenteil: "Daher ist eine thread-sichere Funktion immer wiedereintrittsfähig, aber eine wiedereintrittsfähige Funktion ist nicht immer threadsicher."
4pie0
und Wikipedia sagt noch etwas anderes: "Diese Definition von Wiedereintritt unterscheidet sich von der von Thread-Sicherheit in Umgebungen mit mehreren Threads. Eine Subroutine für Wiedereintritt kann Thread-Sicherheit erreichen, [1] aber Wiedereintritt allein reicht möglicherweise nicht aus, um Thread-sicher zu sein in allen Situationen. Umgekehrt muss threadsicherer Code nicht unbedingt wiedereintrittsfähig sein (...) "
4pie0
@Riccardo: Funktionen, die über flüchtige Variablen, aber nicht über vollständige Speicherbarrieren für die Verwendung mit Signal- / Interrupt-Handlern synchronisiert sind, sind normalerweise wieder verfügbar, aber threadsicher.
Doynax
75

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;
}
MiniQuark
quelle
10
Ich weiß, dass ich nicht nur kommentieren soll, um mich zu bedanken, aber dies ist eine der besten Illustrationen, die die Unterschiede zwischen Wiedereintritts- und Thread-sicheren Funktionen aufzeigt. Insbesondere haben Sie sehr präzise und klare Begriffe verwendet und eine hervorragende Beispielfunktion ausgewählt, um zwischen den vier Kategorien zu unterscheiden. So danke!
Ryyker
Eine Funktion ist sowohl wiedereintrittsfähig als auch threadsicher, wenn sie keine globale / statische Variable verwendet. Thread-sicher: Wenn viele Threads Ihre Funktion gleichzeitig ausführen, gibt es dann ein Rennen? Wenn Sie global var verwenden, verwenden Sie lock, um es zu schützen. es ist also threadsicher. Wiedereintritt: Wenn während der Funktionsausführung ein Signal auftritt und Sie Ihre Funktion erneut im Signal aufrufen, ist dies sicher? In diesem Fall gibt es keine mehreren Threads. Sie dürfen keine statischen / globalen
Variablen verwenden
10
Es scheint mir, dass Beispiel 3 nicht wiedereintrittsfähig ist: Wenn ein Signalhandler, der danach unterbricht t = *x, anruft swap(), twird er überschrieben, was zu unerwarteten Ergebnissen führt.
Rom1v
@MiniQuark wie ist Beispiel 3 wiedereintrittsfähig?
SandBag_1996
4
@ SandBag_1996 Die Annahme ist, dass die Funktion, wenn sie (zu irgendeinem Zeitpunkt) unterbrochen wird, nur erneut aufgerufen werden muss, und wir warten, bis sie abgeschlossen ist, bevor wir den ursprünglichen Aufruf fortsetzen. Wenn etwas anderes passiert, handelt es sich im Grunde genommen um Multithreading, und diese Funktion ist nicht threadsicher. Angenommen, die Funktion führt ABCD aus, wir akzeptieren nur Dinge wie AB_ABCD_CD oder A_ABCD_BCD oder sogar A__AB_ABCD_CD__BCD. Wie Sie überprüfen können, würde Beispiel 3 unter diesen Annahmen gut funktionieren, ist also wiedereintrittsfähig. Hoffe das hilft.
MiniQuark
56

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.

Georg Schölly
quelle
2
Diese Definition von Wiedereintritt ist zu stark.
Qweruiop
Eine Funktion ist sowohl wiedereintrittsfähig als auch threadsicher, wenn sie keine globale / statische Variable verwendet. Thread-sicher: Wenn viele Threads Ihre Funktion gleichzeitig ausführen, gibt es dann ein Rennen? Wenn Sie global var verwenden, verwenden Sie lock, um es zu schützen. es ist also threadsicher. Wiedereintritt: Wenn während der Funktionsausführung ein Signal auftritt und Sie Ihre Funktion erneut im Signal aufrufen, ist dies sicher? In diesem Fall gibt es keine mehreren Threads. Es ist am besten, wenn Sie keine statische / globale
Variable verwenden