5.7 Thread-Synchronisation

# Synchronisation von Threads

Bald wirst Du ausreichend vertraut damit sein, live mit einer Anzahl von 
gleichzeitig ablaufenden Funktionen und Threads zu coden. Dann wirst Du 
bemerken, dass es ziemlich leicht ist, einen einzelnen Thread mit einem
Fehler zum Abbruch zu bringen. Das ist nicht weiter schlimm, da 
Du den Thread mit einem Klick auf `Ausführen` ja einfach neu starten
kannst. Wenn Du den Thread aber neu startest, dann läuft er nicht mehr 
zusammen mit den anderen so wie vorher, er ist nun *aus dem Takt* mit 
den ursprünglichen Threads.

## Vererbte Zeit

Wir haben zuvor erwähnt, dass neue Threads beim Start mit 
`in_thread` alle ihre Einstellungen vom Eltern-Thread erben.
Das schließt auch die aktuelle Zeit mit ein. Das bedeutet, dass 
Threads bei gleichzeitigem Start immer miteinander im Takt sind.

Wenn Du aber einen Thread alleine startest, spielt er in seinem eigenen 
Takt, der höchstwahrscheinlich nicht mit irgendeinem der anderen 
laufenden Threads zusammenpasst.

## Cue und Sync

Sonic Pi hat mit den Funktionen `cue` und `sync` eine Lösung für dieses 
Problem.

`cue` erlaubt es uns, regelmäßige Signale an alle anderen Threads mit
einem Taktgeber zu versenden. Standardmäßig sind die anderen 
Threads an solchen Signale nicht interessiert und werden sie 
ignorieren. Mit der `sync`-Funktion kann man jedoch erreichen, dass ein 
anderer Thread auf diesen Taktgeber hört.

Eine wichtige Sache, derer man sich bewusst sein sollte: `sync` ist so 
ähnlich `sleep`, weil es den aktuellen Thread für eine bestimmte Zeit 
anhält und dieser so lange nichts tut. Allerdings sagt man mit `sleep`,
wie lange man warten möchte, während man bei `sync` nicht weiss, wie
lange gewartet wird - denn `sync` wartet auf das nächste `cue` eines
anderen Threads; das kann kürzer oder länger dauern.

Sehen wir uns das etwas genauer an:

```
in_thread do
  loop do
    cue :tick
    sleep 1
  end
end

in_thread do
  loop do
    sync :tick
    sample :drum_heavy_kick
  end
end
```

Hier haben wir zwei Threads - einer davon arbeitet wie ein Metronom; er 
spielt nichts, aber sendet bei jedem Schlag ein `:tick`-Taktgebersignal. 
Der zweite Thread synchronisiert sich mit den `tick`-Signalen, und wenn 
er eins davon erhält, übernimmt er die Zeit vom `cue`-Thread und läuft 
weiter.

Als Ergebnis hören wir den `:drum_heavy_kick` Sample genau dann, wenn 
der andere Thread ein `:tick`-Signal sendet, auch dann, wenn beide 
Threads gar nicht zur selben Zeit gestartet sind.

```
in_thread do
  loop do
    cue :tick
    sleep 1
  end
end

sleep(0.3)

in_thread do
  loop do
    sync :tick
    sample :drum_heavy_kick
  end
end
```

Dieser freche (zweite) Aufruf von `sleep` würde eigentlich den zweiten 
Thread gegenüber dem ersten aus dem Takt bringen. Weil wir aber `cue` 
und `sync` verwenden, synchronisieren wir beide Threads automatisch und 
umgehen dabei, dass beide in der Zeit auseinanderfallen.

## Cue benennen

Du kannst `cue`-Signale benennen, wie Du willst - nicht nur mit 
`:tick`. Du musst nur sicherstellen, dass irgendein anderer Thread sich 
mit eben diesem Namen synchronisiert - ansonsten wartet der zweite 
Thread dann für immer (oder jedenfalls bis Du den `Stopp`-Button 
klickst).

Lasst uns ein paar Namen für `cue` ausprobieren:

```
in_thread do
  loop do 
    cue [:foo, :bar, :baz].choose
    sleep 0.5
  end
end

in_thread do
  loop do 
    sync :foo 
    sample :elec_beep
  end
end

in_thread do
  loop do
    sync :bar
    sample :elec_flip
  end
end

in_thread do
  loop do
    sync :baz
    sample :elec_blup
  end
end
```

Hier haben wir eine `cue`-Hauptschleife, die auf Zufallsbasis einen von 
drei Namen des Taktgebers `:foo`, `:bar` oder `:baz` aussendet. Wir 
haben auch drei Schleifen-Threads, die jeder für sich mit einem der 
Namen synchronisiert werden und dann jeweils ein anderes Sample spielen.
Im Endeffekt hören wir bei jedem halben Schlag einen Klang, da jeder 
der `sync`-Threads auf Zufallsbasis mit dem `cue`-Thread 
synchronisiert ist, und sein Sample spielt, wenn er dran ist.

Das funktioniert natürlich auch, wenn Du die Reihenfolge der Threads 
umkehrst: Die die `sync`-Threads sitzen einfach da und warten auf ihren 
nächsten `cue`.
