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 Uruchom. 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.

Jednakż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, który jest aktualnie uruchomiony.

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 do wszystkich innych aktualnie uruchomionych wątków. Domyślnie inne wątki nie są tym zainteresowane i ignorują tę wiadomość, jednakże możesz bardzo łatwo wywołać takie zaintrygowanie za pomocą funkcji sync.

Ważną rzeczą, której należy być świadomym, jest to, że funkcja sync wygląda podobnie 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 funkcji sync nie wiesz, jak długo będziesz chciał poczekać, ponieważ ta funkcja 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 z nich 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 słów - 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.