Beschäftigt warten - Busy waiting
In der Informatik und Software - Engineering , busy-Warte , busy-Looping oder Spinnen sind eine Technik , bei der ein Prozess prüft wiederholt , um zu sehen , ob eine Bedingung erfüllt ist, beispielsweise , ob Tastatureingabe oder eine Sperreist verfügbar. Spinning kann auch verwendet werden, um eine beliebige Zeitverzögerung zu erzeugen, eine Technik, die bei Systemen erforderlich war, die keine Methode zum Warten einer bestimmten Zeitdauer hatten. Die Prozessorgeschwindigkeiten variieren stark von Computer zu Computer, insbesondere da einige Prozessoren so ausgelegt sind, dass sie die Geschwindigkeit basierend auf der aktuellen Arbeitsbelastung dynamisch anpassen. Folglich kann das Spinnen als Zeitverzögerungstechnik zu unvorhersehbaren oder sogar inkonsistenten Ergebnissen auf verschiedenen Systemen führen, es sei denn, es ist Code enthalten, um die Zeit zu bestimmen, die ein Prozessor benötigt, um eine "Nichtstun" -Schleife auszuführen , oder der Schleifencode überprüft explizit eine Echtzeituhr .
In den meisten Fällen wird das Spinnen als Anti-Pattern angesehen und sollte vermieden werden, da Prozessorzeit, die für die Ausführung einer anderen Aufgabe verwendet werden könnte, stattdessen für nutzlose Aktivitäten verschwendet wird. Spinning kann unter bestimmten Umständen eine gültige Strategie sein, insbesondere bei der Implementierung von Spinlocks in Betriebssystemen, die auf SMP- Systemen ausgeführt werden sollen.
Beispiel C-Code
Die folgenden C- Codebeispiele veranschaulichen zwei Threads, die eine globale Ganzzahl i gemeinsam verwenden . Der erste Thread verwendet busy-waiting, um zu prüfen, ob sich der Wert von i ändert :
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/* i is global, so it is visible to all functions. It makes use of the special
* type atomic_int, which allows atomic memory accesses.
*/
atomic_int i = 0;
/* f1 uses a spinlock to wait for i to change from 0. */
static void *f1(void *p)
{
int local_i;
/* Atomically load current value of i into local_i and check if that value
is zero */
while ((local_i = atomic_load(&i)) == 0) {
/* do nothing - just keep checking over and over */
}
printf("i's value has changed to %d.\n", local_i);
return NULL;
}
static void *f2(void *p)
{
int local_i = 99;
sleep(10); /* sleep for 10 seconds */
atomic_store(&i, local_i);
printf("t2 has changed the value of i to %d.\n", local_i);
return NULL;
}
int main()
{
int rc;
pthread_t t1, t2;
rc = pthread_create(&t1, NULL, f1, NULL);
if (rc != 0) {
fprintf(stderr, "pthread f1 failed\n");
return EXIT_FAILURE;
}
rc = pthread_create(&t2, NULL, f2, NULL);
if (rc != 0) {
fprintf(stderr, "pthread f2 failed\n");
return EXIT_FAILURE;
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
puts("All pthreads finished.");
return 0;
}
In einem solchen Anwendungsfall kann man sich überlegen, die Bedingungsvariablen von C11 zu verwenden .
Alternativen
Die meisten Betriebssysteme und Threading - Bibliotheken bieten eine Vielzahl von Systemaufrufen , die wird blockiert den Prozess auf ein Ereignis, wie Lock - Acquisition, Timer Änderungen, E / A - Verfügbarkeit oder Signale . Unter Verwendung solcher Anrufe erzeugt im Allgemeinen die einfachste, effizienteste, faire und Rennen -freien Ergebnis. Ein einzelner Aufruf prüft, informiert den Scheduler über das Ereignis, auf das er wartet, fügt gegebenenfalls eine Speicherbarriere ein und kann vor der Rückkehr eine angeforderte E/A-Operation ausführen. Andere Prozesse können die CPU verwenden, während der Aufrufer blockiert ist. Der Scheduler erhält die Informationen, die er benötigt, um die Prioritätsvererbung oder andere Mechanismen zu implementieren , um ein Verhungern zu vermeiden .
Das Besetzt-Warten selbst kann viel weniger verschwenderisch gemacht werden, indem eine Verzögerungsfunktion (zB sleep()) verwendet wird, die in den meisten Betriebssystemen zu finden ist. Dadurch wird ein Thread für eine bestimmte Zeit in den Ruhezustand versetzt, während der der Thread keine CPU-Zeit verschwendet. Wenn die Schleife etwas Einfaches überprüft, verbringt sie die meiste Zeit im Schlaf und verschwendet nur sehr wenig CPU-Zeit.
In Programmen, die niemals enden (z. B. in Betriebssystemen), kann unendliches Beschäftigt-Warten implementiert werden, indem unbedingte Sprünge verwendet werden, wie in dieser NASM- Syntax gezeigt: jmp $. Die CPU springt bedingungslos für immer an ihre eigene Position . Eine geschäftige Wartezeit wie diese kann ersetzt werden durch:
sleep:
hlt
jmp sleep
Weitere Informationen finden Sie unter HLT (x86-Anweisung) .
Angemessene Verwendung
Bei der Low-Level-Programmierung können Busy-Waits tatsächlich wünschenswert sein. Es kann nicht wünschenswert oder praktisch sein, eine unterbrechungsgesteuerte Verarbeitung für jedes Hardwaregerät zu implementieren, insbesondere für diejenigen, auf die selten zugegriffen wird. Manchmal ist es notwendig, irgendeine Art von Steuerdaten in die Hardware zu schreiben und dann den aus dem Schreibvorgang resultierenden Gerätestatus abzurufen, der möglicherweise erst nach Ablauf einer Anzahl von Maschinenzyklen nach dem Schreiben gültig wird. Der Programmierer könnte eine Verzögerungsfunktion des Betriebssystems aufrufen, dies kann jedoch mehr Zeit in Anspruch nehmen, als beim Drehen für einige Taktzyklen aufgewendet werden würde, um darauf zu warten, dass das Gerät seinen Status zurückgibt.
Siehe auch
- Umfragen (Informatik)
- Nicht blockierende E/A
- Spinlock
- BogoMips
- flüchtige Variable
- Synchronisation (Informatik)
- Petersons Algorithmus
Verweise
Externe Links
- Beschreibung aus The Open Group Base Specifications Issue 6, IEEE Std 1003.1, 2004 Edition
- Artikel " Spin Locks auf Benutzerebene - Threads, Prozesse & IPC " von Gert Boddaert
- Österreich SpinLock-Klassenreferenz