Synchronizacja Wątków

Skoro osiągnąłeś już wystarczająco zaawansowane umiejętności kodowania na żywo (live coding) wraz z wykorzystaniem jednocześnie funkcji i wątków, na pewno zauważyłeś już, że bardzo łatwo jest popełnić błąd w jednym z wątków co powoduje, że zostaje on zabity. To nie jest wielka rzecz, ponieważ możesz bardzo łatwo zrestartować wątek poprzez ponowne naciśnięcie przycisku Run. Jednakże, kiedy zrestartujesz wątek to wypadnie on teraz z rytmu oryginalnych wątków.

Odziedziczony Czas

Jak już mówiliśmy wcześniej, nowe wątki tworzone z wykorzystaniem polecenia in_thread dziedziczą wszystkie ustawienia z wątku rodzica. W skład tych ustawień wchodzi też aktualny czas. Oznacza to, że wątki pozostają zawsze w korelacji czasowej kiedy zostaną uruchomione jednocześnie.

Jendakże, kiedy uruchomisz samodzielny wątek, rozpoczyna się on ze swoim własnym czasem i jest mało prawdopodobne, że będzie on w synchronizacji z jakimkolwiek innym uruchomionym aktualnie wątkiem.

Cue (Wskazówka) i Sync (Synchronizacja)

Sonic Pi udostępnia rozwiązanie dla tego problemu za pomocą funkcji cue (wskazówka) i sync (synchronizacja).

cue pozwala nam na wysłanie wiadomości o pulsie to wszystkich innych aktualnie uruchomionych wątków. Domyślnie inne wątki nie są tym zainteresowane i ignorują tę wiadomość o pulsie. Jendakże, możesz bardzo łatwo wywołać takie zainteresowanie za pomocą funkcji sync.

Ważną rzeczą, której należy być świadomym jest to, że funkcja sync jest podobna do funkcji sleep w taki sposób, że powstrzymuje ona aktualny wątek od robienia czegokolwiek przez pewien okres czasu. Jednakże, w przypadku funkcji sleep musisz zdefiniować jak długo chcesz poczekać, natomiast w przypadku funkcji sync nie wiesz jak długo będziesz chicał poczekać, ponieważ funkcja sync czeka na następny punkt cue z innego wątku, co może nastąpić szybciej lub później.

Spróbujmy przyjrzeć się temu trochę bardziej dokładnie:

in_thread do
  loop do
    cue :tick
    sleep 1
  end
end

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

Mamy tutaj dwa wątki - jeden zachowuje się jak metronom, nie gra żadnych dźwięków tylko wysyła komunikat :tik (cyk) przy każdym uderzeniu. Drugi wątek synchronizuje się natomiast przy otrzymaniu każdej kolejnej wiadomości :tick i kiedy otrzymuje kolejną odziedzicza czas uruchomienia cue i rozpoczyna swoje działanie.

Jako wynik słyszymy sampel :drum_heavy_kick dokładnie w tym samym momencie gdy inny wątek wysyła komunikat :tick, nawet jeśli obydwa wątki nie zostały uruchomione w tym samym czasie.

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

Te niegrzeczne polecenie sleep normalnie stworzyłoby drugi wątek, który byłby zupełnie niezsynchronizowany z pierwszym. Jednakże, dzięki użyciu poleceń cue i sync automatycznie synchronizujemy wątki blokując jednocześnie jakiekolwiek nieprzewidziane rozjazdy w czasie.

Nazwy dla komunikatów Cue

Dla nazw twoich komunikatów cue możesz używać dowolnych nazw - może to być nie tylko :tick. Musisz się tylko upewnić, że inne wątki synchronizują się z właściwą nazwą - w przeciwnym wypadku będą czekać w nieskończoność (albo do momentu, w którym naciśniesz przycisk Stop).

Spróbujmy pobawić się z kilkoma komunikatami cue o różnych nazwach:

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

Mamy tutaj główną pętlę cue, która co iterację wysyła komunikat z losową wybraną nazwą punktu cue do synchronizacji - :foo, :bar lub :baz. Następnie mamy trzy wątki z pętlami, które synchronizują się na każdym z tych komunikatów niezależnie i odtwarzają różne sample. Wynikiem tego jest to, że słyszymy dźwięk co każde 0.5 uderzenia jako, że każdy z synchronizowanych (sync) wątków jest wybierany losowo z wątku cue po czym odtwarza swojego sampla.

Oczywiście to wszystko zadziała analogicznie nawet jeśli poukładasz wątki w odwrotnej kolejności jako, że wątek sync będzie po prostu czekał na kolejną wartość wysłaną przez wątek cue.