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

Verweise

Externe Links