Sincronizzazione dei thread

Raggiungendo un livello avanzato di live coding e usando molte funzioni e thread simultaneamente, ti sarai accordo che è semplice fare un errore che ferma un thread. Non è un problema perché puoi sempre farlo ripartire premendo Run ma, quando si riavvierà, sarà fuori tempo rispetto al thread originale.

Ereditare il tempo

Come abbiamo discusso in precedenza, i nuovi thread creati con in_thread ereditano le impostazioni dal thread principale. Questo include, ovviamente, anche il tempo. Questo significa che i thread sono sempre a tempo quando partono in simultanea.

Tuttavia, quando fai partire un thread da solo, questo partirà con il suo tempo ed è davvero improbabile che sia sincronizzato con gli altri thread.

Segnale (cue) e sincronizzazione

Sonic Pi offre una soluzione a questo problema con le funzioni cue e sync.

La funzione cueci permette di inviare il messaggio del cuore pulsante a tutti gli altri thread. Di default, gli altri thread non sono interessati a ricevere questo messaggio e lo ignorano. È possibile, però, attivare l’interesse al segnale utilizzando la funzione sync.

La cosa importante da sapere è che sync funziona in modo simile a sleep nel senso che ferma il thread corrente per un certo periodo di tempo. La differenza è che con sleep specifichiamo quanto tempo aspettare mentre con sync non è possibile sapere quando aspetterà dal momento che sync aspetta il segnale cue da un altro thread che potrebbe essere vicino oppure lontano nel tempo.

Proviamo ad andare nel dettaglio:

in_thread do
  loop do
    cue :tick
    sleep 1
  end
end
in_thread do
  loop do
    sync :tick
    sample :drum_heavy_kick
  end
end

Qui abbiamo due thread, uno che si comporta come un metronomo e non riproduce alcun suono ma invia il segnale :tick a ogni battito. Il secondo thread è sincronizzato al messaggio tick e quando ne riceve uno, eredita il tempo del thread cuee continua a funzionare.

Di conseguenza, sentiremo il campione :drum_heavy_kick esattamente nel momento in cui l’altro thread invia il messaggio :tick, anche se i due thread non vengono avviati in contemporanea:

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

Quella chiamata a sleep normalmente manderebbe il secondo thread fuori fase rispetto al primo, ma, dal momento che usiamo cue e sync, sincronizziamo automaticamente i thread bypassando ogni possibile deviazione di tempo.

Nominare i segnali di cue

Sei libero di usare tutti i nomi che vuoi per i messaggi cue, non solo :tick. Devi solo assicurarti che tutti i thread siano sincronizzati sullo stesso nome, altrimenti aspetteranno all’infinito (o, fino alla pressione del pulsante di Stop).

Facciamo qualche prova con diversi nomi per cue:

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

Qui abbiamo un loop principale cue che in modo casuale invia segnali chiamati :foo, :bar o :baz. Abbiamo poi tre thread di loop con suoni differenti, ciascuno dei quali si sincronizza con quei segnali in modo indipendente. L’effetto rete fa si che sentiamo un suono ogni 0.5 battiti dal momento che ciascuno dei thread sync è sincronizzato in modo casuale con i thread cue che riproducono il campione.

Questo ovviamente funziona se ordini i thread al contrario dal momento che i thread sync aspetteranno di ricevere il cue successivo.