Wiedereintrittsfähigen und threadsicheren Code schreiben
In Einzelthreadprozessen ist nur ein Steuerungsfluss vorhanden. Der von diesen Prozessen ausgeführte Code muss daher nicht simultan oder threadsicher sein. In Multithread-Programmen können mehrere Steuerungsabläufe gleichzeitig auf dieselben Funktionen und Ressourcen zugreifen.
Um die Ressourcenintegrität zu schützen, muss der für Multithread-Programme geschriebene Code wiedereintrittsfähig und threadsicher sein.
Wiedereinstieg und Threadsicherheit hängen beide von der Art und Weise ab, wie Funktionen mit Ressourcen umgehen. Wiedereinstieg und Threadsicherheit sind separate Konzepte: Eine Funktion kann entweder wiedereintrittsfähig, threadsicher, beides oder keines von beiden sein.
Dieser Abschnitt enthält Informationen zum Schreiben von wiedereintrittsfähigen und threadsicheren Programmen. Das Thema des Schreibens von threadeffizienten Programmen wird nicht behandelt. Threadeffiziente Programme sind effizient parallelisierte Programme. Sie müssen die Threadeffizienz beim Entwurf des Programms berücksichtigen. Vorhandene Einzelthreadprogramme können threadeffizient gemacht werden, dies erfordert jedoch eine vollständige Neugestaltung und Neuerstellung.
Wiedereintritt
Eine wiedereintrittsfähige Funktion enthält keine statischen Daten über aufeinanderfolgende Aufrufe und gibt keinen Zeiger auf statische Daten zurück. Alle Daten werden vom Aufrufenden der Funktion bereitgestellt. Eine wiedereintrittsfähige Funktion darf keine nicht wiedereintrittsfähigen Funktionen aufrufen.
Eine nicht wiedereintrittsfähige Funktion kann häufig, aber nicht immer durch ihre externe Schnittstelle und ihre Nutzung identifiziert werden. Die Subroutine strtok ist beispielsweise nicht simultan verwendbar, da sie die Zeichenfolge enthält, die in Tokens aufgeteilt werden soll. Die Subroutine ctime ist ebenfalls nicht simultan verwendbar; sie gibt einen Zeiger auf statische Daten zurück, die von jedem Aufruf überschrieben werden.
Threadsicherheit
Eine threadsichere Funktion schützt gemeinsam genutzte Ressourcen vor gleichzeitigem Zugriff durch Sperren. Die Threadsicherheit betrifft nur die Implementierung einer Funktion und hat keine Auswirkungen auf ihre externe Schnittstelle.
/* threadsafe function */
int diff(int x, int y)
{
int delta;
delta = y - x;
if (delta < 0)
delta = -delta;
return delta;
}Die Verwendung globaler Daten ist threadunsicher. Globale Daten sollten pro Thread verwaltet oder eingebunden werden, damit ihr Zugriff serialisiert werden kann. Ein Thread kann einen Fehlercode lesen, der einem Fehler entspricht, der von einem anderen Thread verursacht wurde. In AIX® hat jeder Thread seinen eigenen errno-Wert.
Funktion wiedereintrittsfähig machen
In den meisten Fällen müssen nicht wiedereintrittsfähige Funktionen durch Funktionen mit einer geänderten Schnittstelle ersetzt werden, um wiedereintrittsfähig zu sein. Nicht wiedereintrittsfähige Funktionen können nicht von mehreren Threads verwendet werden. Außerdem kann es unmöglich sein, eine nicht wiedereintrittsfähige Funktion threadsicher zu machen.
Daten zurückgeben
- Dynamisch zugeordnete Daten zurückgeben. In diesem Fall liegt es in der Verantwortung des Anrufers, den Speicher freizugeben. Der Vorteil ist, dass die Schnittstelle nicht geändert werden muss. Die Abwärtskompatibilität ist jedoch nicht gewährleistet; vorhandene Einzelthreadprogramme, die die geänderten Funktionen ohne Änderungen verwenden, würden den Speicher nicht freigeben, was zu Speicherlecks führt.
- Vom Aufrufenden bereitgestellter Speicher wird verwendet Diese Methode wird empfohlen, obwohl die Schnittstelle geändert werden muss.
/* non-reentrant function */
char *strtoupper(char *string)
{
static char buffer[MAX_STRING_SIZE];
int index;
for (index = 0; string[index]; index++)
buffer[index] = toupper(string[index]);
buffer[index] = 0
return buffer;
}/* reentrant function (a poor solution) */
char *strtoupper(char *string)
{
char *buffer;
int index;
/* error-checking should be performed! */
buffer = malloc(MAX_STRING_SIZE);
for (index = 0; string[index]; index++)
buffer[index] = toupper(string[index]);
buffer[index] = 0
return buffer;
}/* reentrant function (a better solution) */
char *strtoupper_r(char *in_str, char *out_str)
{
int index;
for (index = 0; in_str[index]; index++)
out_str[index] = toupper(in_str[index]);
out_str[index] = 0
return out_str;
}Die nicht wiedereintrittsfähigen Standard-C-Bibliothekssubroutinen wurden wiedereintrittsfähig gemacht, indem Speicher verwendet wurde, der vom aufrufenden Programm bereitgestellt wurde.
Daten über aufeinanderfolgende Aufrufe hinweg beibehalten
Über aufeinanderfolgende Aufrufe sollten keine Daten aufbewahrt werden, da die Funktion von verschiedenen Threads nacheinander aufgerufen werden kann. Wenn eine Funktion einige Daten über aufeinanderfolgende Aufrufe verwalten muss, z. B. einen Arbeitspuffer oder einen Zeiger, sollte der Aufrufende diese Daten bereitstellen.
/* non-reentrant function */
char lowercase_c(char *string)
{
static char *buffer;
static int index;
char c = 0;
/* stores the string on first call */
if (string != NULL) {
buffer = string;
index = 0;
}
/* searches a lowercase character */
for (; c = buffer[index]; index++) {
if (islower(c)) {
index++;
break;
}
}
return c;
}h
/* reentrant function */
char reentrant_lowercase_c(char *string, int *p_index)
{
char c = 0;
/* no initialization - the caller should have done it */
/* searches a lowercase character */
for (; c = string[*p_index]; (*p_index)++) {
if (islower(c)) {
(*p_index)++;
break;
}
}
return c;
}char *my_string;
char my_char;
int my_index;
...
my_index = 0;
while (my_char = reentrant_lowercase_c(my_string, &my_index)) {
...
}Funktion threadsicher machen
In Multithread-Programmen müssen alle Funktionen, die von mehreren Threads aufgerufen werden, threadsicher sein. Es gibt jedoch eine Fehlerumgehung für die Verwendung von threadunsicheren Subroutinen in Multithread-Programmen. Nicht wiedereintrittsfähige Funktionen sind normalerweise threadunsicher, aber wenn sie wiedereintrittsfähig gemacht werden, sind sie oft auch threadsicher.
Gemeinsam genutzte Ressourcen sperren
/* thread-unsafe function */
int increment_counter()
{
static int counter = 0;
counter++;
return counter;
}/* pseudo-code threadsafe function */
int increment_counter();
{
static int counter = 0;
static lock_type counter_lock = LOCK_INITIALIZER;
pthread_mutex_lock(counter_lock);
counter++;
pthread_mutex_unlock(counter_lock);
return counter;
}In einem Multithread-Anwendungsprogramm, das die Threadbibliothek verwendet, sollten Mutexe für die Serialisierung gemeinsam genutzter Ressourcen verwendet werden. Unabhängige Bibliotheken müssen möglicherweise außerhalb des Kontexts von Threads arbeiten und andere Arten von Sperren verwenden.
Problemumgehungen für threadunsichere Funktionen
- Verwenden Sie eine globale Sperre für die Bibliothek und sperren Sie sie jedes Mal, wenn Sie die Bibliothek verwenden (Aufruf einer Bibliotheksroutine oder Verwendung einer globalen Bibliotheksvariablen). Diese Lösung kann Leistungsengpässe verursachen, da jeweils nur ein Thread auf jeden Teil der Bibliothek zugreifen kann. Die Lösung im folgenden Pseudocode ist nur akzeptabel, wenn selten auf die Bibliothek zugegriffen wird oder als erste, schnell implementierte Problemumgehung.
/* this is pseudo code! */ lock(library_lock); library_call(); unlock(library_lock); lock(library_lock); x = library_var; unlock(library_lock); - Verwenden Sie eine Sperre für jede Bibliothekskomponente (Routine oder globale Variable) oder Komponentengruppe. Diese Lösung ist etwas komplizierter zu implementieren als das vorherige Beispiel, kann aber die Leistung verbessern. Da diese Problemumgehung nur in Anwendungsprogrammen und nicht in Bibliotheken verwendet werden sollte, können Mutexe zum Sperren der Bibliothek verwendet werden.
/* this is pseudo-code! */ lock(library_moduleA_lock); library_moduleA_call(); unlock(library_moduleA_lock); lock(library_moduleB_lock); x = library_moduleB_var; unlock(library_moduleB_lock);
Wiedereintrittsfähige und threadsichere Bibliotheken
Wiedereintrittsfähige und threadsichere Bibliotheken sind in einer Vielzahl von parallelen (und asynchronen) Programmierumgebungen nützlich, nicht nur innerhalb von Threads. Es ist eine gute Programmierpraxis, immer wiedereintrittsfähige und threadsichere Funktionen zu verwenden und zu schreiben.
Bibliotheken verwenden
- Standard-C-Bibliothek (libc.a)
- Berkeley-Kompatibilitätsbibliothek (libbsd.a)
Einige der Standard-C-Subroutinen sind nicht wiedereintrittsfähig, z. B. die Subroutinen ctime und strtok . Die simultan verwendbare Version der Subroutine hat den Namen der ursprünglichen Subroutine mit dem Suffix _r (Unterstreichungszeichen gefolgt vom Buchstaben r).
token[0] = strtok(string, separators);
i = 0;
do {
i++;
token[i] = strtok(NULL, separators);
} while (token[i] != NULL);char *pointer;
...
token[0] = strtok_r(string, separators, &pointer);
i = 0;
do {
i++;
token[i] = strtok_r(NULL, separators, &pointer);
} while (token[i] != NULL);Threadunsichere Bibliotheken können nur von einem Thread in einem Programm verwendet werden. Stellen Sie die Eindeutigkeit des Threads sicher, der die Bibliothek verwendet. Andernfalls weist das Programm ein nicht erwartetes Verhalten auf oder kann sogar gestoppt werden.
Bibliotheken konvertieren
- Geben Sie exportierte globale Variablen an. Diese Variablen werden normalerweise in einer Headerdatei mit dem Schlüsselwort export definiert. Exportierte globale Variablen sollten eingebunden werden. Die Variable sollte privat gemacht werden (definiert mit dem Schlüsselwort static im Quellcode der Bibliothek), und es sollten Zugriffssubroutinen (Lesen und Schreiben) erstellt werden.
- Statische Variablen und andere gemeinsam genutzte Ressourcen identifizieren. Statische Variablen werden normalerweise mit dem Schlüsselwort static definiert. Sperren sollten allen gemeinsam genutzten Ressourcen zugeordnet sein. Die Granularität der Sperre, d. h. die Anzahl der Sperren, wirkt sich auf die Leistung der Bibliothek aus. Zum Initialisieren der Sperren kann die einmalige Initialisierungsfunktion verwendet werden.
- Identifizieren Sie nicht wiedereintrittsfähige Funktionen und machen Sie sie wiedereintrittsfähig. Weitere Informationen finden Sie unter Funktion wiedereintrittsfähig machen.
- Identifizieren Sie threadunsichere Funktionen und machen Sie sie threadsicher. Weitere Informationen finden Sie unter Making a Function threadsafe.