Sincronizarea firelor de executie

Cand ai devenit suficient de avansat sa programezi live cu un numar de functii si thread-uri simultane, ai observat probabil ca este destul de usor sa faci o greseala intr-un thread astfel incat sa il blochezi. Asta nu e o mare problema, fiindca poti reporni usor thread-ul apasand pe Run. Totusi, daca repornesti un thread el va fi decalat fata de thread-urile originale.

Mostenirea pozitiei in timp

Asa cum am discutat anterior, thread-urile noi create cu ‘in_thread’ mostenesc toti parametrii de la thread-ul parinte. Asta include si pozitia in timp. Acest lucru inseamna ca thread-urile sunt mereu aliniate in timp daca au pornit simultan.

Totusi, daca pornesti un thread de sine statator, el va porni cu timpul sau propriu care este putin probabil sa fie sincronizat cu cel al altor thread-uri care ruleaza la momentul respectiv.

Marcaje de timp si sincronizare

Sonic Pi ofera o solutie pentru aceast problema cu functiile ‘cue’ (marcaj de timp) si ‘sync’ (sincronizare).

‘cue’ ne permite sa transmitem mesaje referitoare la tact catre alte thread-uri. Implicit celelalte thread-uri nu sunt interesate si vor ignora aceste mesaje. Totusi, poti sa le determini cu usurinta sa devina interesate folosind functia ‘sync’.

Trebuie sa fii constient ca ‘sync’ este oarecum similar cu ‘sleep’ prin aceea ca opreste thread-ul curent sa faca orice pentru o anumita perioada de timp. Totusi, cu ‘sleep’ tu decizi cat vrei sa dureze pauza, in timp ce cu ‘sync’ nu stii cat timp va dura asteptare, deoarece ‘sync’ asteapta urmatorul ‘cue’ de la alt thread, care poate sosi mai devreme sau mai tarziu.

Sa examinam acest lucru mai in detaliu:

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

Aici avem doua thread-uri, unul actionand ca un metronom, fara sa redea vreun sunet, dar trimitand mesaje ‘:tick’ corespunzator tactului la fiecare bataie. Al doilea thread este sincronizat cu mesajele ‘tick’ si cand primeste unul mosteneste timpul din thread-ul ‘cue’ continuand sa ruleze.

Ca urmare, vom auzi esantionul :drum_heavy_kick exact cand cealalt thread trimite mesajul ‘:tick’, chiar daca cele doua thread-uri nu au fost pornite in acelasi timp:

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

Acel apel buclucas al functiei ‘sleep’ ar face in mod normal ca al doilea thread sa fie defazat fata de primul. Totusi, folosind ‘cue’ si ‘sync’, putem sincroniza automat thread-urile trecand peste decalajele accidentale.

Nume pentru marcajele de timp

Esti liber sa folosesti ce nume doresti pentru mesajele ‘cue’, nu doar ‘:tick’. Trebuie doar sa te asiguri ca celelalte thread-uri se sincronizeaza dupa numele corect - in caz contrar ar putea astepta la infinit (sau pana cand apesi butonul Stop).

Sa ne jucam putin cu numele pentru marcajele ‘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

Aici avem o bucla ‘cue’ principala care trimite aleator un tact dintre :foo, :bar si :baz. Avem apoi trei thread-uri de tip bucla care se sincronizeaza fiecare cu unul dintre cele trei nume si redau fiecare un alt esantion. Efectul final este ca auzim un sunet la fiecare 0.5 batai deoarece de fiecare data unul dintre thread-urile ‘sync’ se va sincroniza cu thread-ul ‘cue’ si va reda propriul esantion.

Acest lucru va fi valabil desigur si daca schimbi ordinea thread-urilor, deoarece thread-urile ‘sync’ vor sta si vor astepta pur si simplu urmatorul ‘cue’.