Busy-Wait-Schleife
Unter einer Busy-Wait-Schleife (von engl. beschäftigtes Warten) versteht man eine Warteschleife in einem Programm, die unablässig eine Bedingung überprüft, bis diese eingetreten ist. Dadurch ist der Computer die ganze Zeit über mit dieser Abfrage beschäftigt und kann nichts anderes mehr machen, weshalb Alternativen, wenn sie denn existieren, die bessere Wahl darstellen.
Wenn man beispielsweise darauf wartet, dass es 18 Uhr ist, könnte man alle 5 Sekunden auf die Uhr schauen, bis es so weit ist. Das wäre dann Busy-Wait, weil man ja beim Warten die ganze Zeit über mit dem Schauen auf die Uhr beschäftigt ist. Die Alternative, sich einen Wecker zu stellen der um 18 Uhr piept, hat diesen Nachteil nicht.
BASIC[Bearbeiten | Quelltext bearbeiten]
In BASIC gibt es im Wesentlichen zwei Möglichkeiten, eine Busy-Wait-Schleife zu implementieren: Mit dem WAIT-Befehl kann auf gewisse Bit-Pattern in einer Speicherzelle gewartet werden. Das ist vor allem für zeitkritische Anwendungen (für ein exaktes Timing) nützlich, wie beispielsweise einem Sprite-Multiplexer in BASIC.
Alternativ dazu kann eine Bedingung in einer Schleife überprüft werden. Um beispielsweise auf eine bestimmte Uhrzeit zu warten kann man wie folgt vorgehen:
10 TI$ = "175930" 20 IF LEFT$(TI$,2) <> "18" GOTO20 30 PRINT "ALARM ES IST "TI$" UHR"
In Zeile 10 wird die aktuelle Zeit auf 17:59:30 gesetzt. Zeile 20 wartet in einer Busy-Wait-Schleife darauf, dass die ersten beiden Ziffern zu einer 18 werden. Das Programm wartet somit 30 Sekunden lang.
In BASIC 7.0 gibt es als dritte Möglichkeit noch den SLEEP-Befehl, mit dem eine gewisse Dauer gewartet wird. Allerdings ist dieser, anders als der gleichlautende Unix-Befehl ebenfalls als Busy-Wait implementiert, weshalb der Computer in der Wartezeit auch hier nicht anderweitig genutzt werden kann.
Alternativen[Bearbeiten | Quelltext bearbeiten]
Im C64-BASIC gibt es keine alternative Möglichkeit. Das einzige, was man hier machen kann, ist die Wartezeit sinnvoll zu nutzen, indem man in der Warteschleife noch andere Befehle ausführt. Dadurch wird allerdings das Timing möglicherweise etwas ungenauer. Im Falle der Tastaturabfrage wird das durch den Tastaturpuffer allerdings abgefedert (der auch dann gefüllt wird, wenn noch nicht aktiv auf eine Taste abgefragt wird). Das folgende Programm füllt während des Wartens den Bildschirm mit Sternchen:
10 TI$ = "175930" 20 PRINT "*"; : REM WARTEZEIT SINNVOLL NUTZEN 30 IF LEFT$(TI$,2) <> "18" GOTO20 40 PRINT "ALARM ES IST "TI$" UHR"
Unter BASIC 7.0 kann ein Busy-Wait mithilfe einer speziellen Konstruktion umgangen werden, indem sich ein (unsichtbar) automatisch bewegtes Sprite eine Kollision auslöst und dadurch eine BASIC-Routine aufruft (via COLLISION). Damit sind zeitgesteuerte BASIC-Handler vergleichbar mit einer IRQ bei Maschinenprogrammen möglich. So sind auch feine Zeitintervalle in einer Auflösung von 1/50 s behandelbar. Siehe COLLISION-Beispiel.
In der BASIC-Erweiterung TSB gibt es mit dem Schlüsselwort ON KEY eine Möglichkeit, ohne Busy-Wait-Schleife auf einen Tastendruck zu warten.
Assembler[Bearbeiten | Quelltext bearbeiten]
Das nachfolgende Assemblerprogramm in ACME-Syntax wartet in einer Busy-Wait-Schleife darauf, dass es auf der Echtzeituhr des CIA1-Bausteins 18:00 Uhr ist:
!to "busywait.prg", cbm *=$c000 lda $DC0F ; Uhrzeit setzen and #$7F sta $DC0F lda $DC0E ; 50 Hz ora #$80 sta $DC0E lda #$85 ; 5 Uhr PM = 17 Uhr sta $DC0B lda #$59 ; 59 Minuten sta $DC0A lda #$30 ; 30 Sekunden sta $DC09 lda #$00 ; 0 Zehntelsekunden sta $DC08 - lda $DC0B ; Busy-Wait-Schleife cmp #$86 ; Vergleicht die Stunden mit 18 Uhr beq + lda $DC08 ; Zehntelsekunden müssen gelesen werden jmp - ; damit die nächste Abfrage der Stunden ; richtig funktioniert + ldx #$00 ; Alarm-Meldung ausgeben beq + - jsr $FFD2 ; Zeichen ausgeben inx + lda .alarm,x bne - rts .alarm !pet 13, "alarm!", 13, 0
Alternativen[Bearbeiten | Quelltext bearbeiten]
Beim C64 kann man mit Assembler in etlichen Fällen die Nutzung einer Busy-Wait-Schleife umgehen. (Wenn das nicht geht, kann man zumindest, wie bei BASIC auch, die Wartezeit für zusätzliche Befehle nutzen.) Möglich wird dies durch die beiden Interrupts (IRQ und NMI). Die zu überprüfende Bedingung löst in diesen Fällen einen der beiden Interrupts aus und in der Interrupt-Routine kann dann darauf reagiert werden.
Die folgenden Bedingungen können einen IRQ auslösen:
- Bei einem der beiden Timer der CIA1 findet ein Unterlauf statt.
- Einer der beiden Timer der CIA1 erreicht die Alarmzeit.
- Das Schieberegister der CIA1 ist abgearbeitet.
- Der FLAG-Pin der CIA1 geht auf Low.
- Der Rasterstrahl erreicht eine neue Zeile.
- Zwei Sprites kollidieren.
- Ein Sprite kollidiert mit dem Hintergrund.
- Der Lightpen wird benutzt.
- Am Expansionsport wird PIN 4 auf Low gesetzt. Hierdurch haben Steckmodule die Möglichkeit, eigene Bedingungen zu generieren.
Die folgenden Bedingungen können einen NMI auslösen:
- Bei einem der beiden Timer der CIA2 findet ein Unterlauf statt.
- Einer der beiden Timer der CIA2 erreicht die Alarmzeit.
- Das Schieberegister der CIA2 ist abgearbeitet.
- Der FLAG-Pin der CIA2 geht auf Low.
- Der Benutzer drückt die RESTORE-Taste.
- Am Expansionsport wird Pin D auf Low gesetzt. Hierdurch haben Steckmodule die Möglichkeit, eigene Bedingungen zu generieren.
- Am Userport wird Pin FLAG 22 auf Low gesetzt. Hierdurch kann über die Serielle Schnittstelle ein Interrupt ausgelöst werden.
Beispiel[Bearbeiten | Quelltext bearbeiten]
Das nachfolgende Beispiel in ACME-Syntax nutzt die Alarmzeit der Echtzeituhr des CIA1 um ein Busy-Wait zu umgehen. Nach dem Aufruf mit SYS 49152 kehrt das Programm fast augenblicklich zur Eingabe zurück. Man kann jetzt zum Beispiel ein BASIC-Programm eingeben. Nach 30 Sekunden wird dann allerdings durch die IRQ-Routine das Wort "ALARM" auf dem Bildschirm ausgegeben.
!to "irqwait.prg", cbm *=$c000 lda $DC0F ; Uhrzeit setzen and #$7F sta $DC0F lda $DC0E ; 50 Hz ora #$80 sta $DC0E lda #$85 ; 5 Uhr PM = 17 Uhr sta $DC0B lda #$59 ; 59 Minuten sta $DC0A lda #$30 ; 30 Sekunden sta $DC09 lda #$00 ; 0 Zehntelsekunden sta $DC08 lda $DC0F ; Alarmzeit setzen ora #$80 sta $DC0F lda #$86 ; 6 Uhr PM = 18 Uhr sta $DC0B lda #$00 ; 0 Minuten, Sekunden und Zehntelsekunden sta $DC0A sta $DC09 sta $DC08 sei ; Interrupt verbiegen lda #<.irq sta $0314 lda #>.irq sta $0315 lda #$84 ; Alarmzeit-IRQ anschalten sta $DC0D cli rts .irq lda $DC0D bne + cli ; kein CIA-Interrupt, deshalb - jmp $ea31 ; System-Interrupt aufrufen + and #$84 cmp #$84 bne - ; kein Alarmzeit-Interrupt ldx #$00 ; Alarm-Meldung ausgeben beq + - jsr $ffd2 ; Zeichen ausgeben inx + lda .alarm,x bne - pla ; Aufräumen und Interrupt tay ; beenden. pla tax pla rti .alarm !pet 13, "alarm!", 13, 0
Moderne Betriebssysteme und moderne Prozessoren[Bearbeiten | Quelltext bearbeiten]
Bei anderen Betriebssystemen gibt es weitere Möglichkeiten eine Busy-Wait-Schleife zu vermeiden. Allen voran sind multitaskingfähige Betriebssysteme in der Lage das Programm (als eigenständigen "Task") so lange anzuhalten, bis eine bestimmte Bedingung eintrifft, während andere Tasks weiter abgearbeitet werden.
Bei modernen Prozessoren ist es möglich, diese in einen Ruhezustand zu versetzen, sodass während des Wartens der Stromverbrauch (und damit auch die Wärmeentwicklung) reduziert wird.
Weblinks[Bearbeiten | Quelltext bearbeiten]
Wikipedia: Aktives Warten |
Wikipedia: Busy waiting |