Sincronização de Threads

Quando te tornares suficientemente avançado em live coding com uma serie de funções e threads simultaneamente, irás notar que é bastante fácil fazer em erro num dos threads e o matar. Isso não é muito importante porque o podes reiniciar carregando no Run. No entanto, quando reinicias o thread ele está fora de tempo com os threads originais.

Herança de tempo

Como discutimos anteriormente, novos threads criados com in_thread herdam todas as definições do thread pai. Isto inclui o tempo corrente. Isto significa que os threads estarão sempre sincronizados uns com os outros quando começarem simultaneamente.

No entanto, quando começas um thread por si só, o seu tempo é improvável estar sincronizado com qualquer dos outros threads em execução.

Fila e sincronização

O Sonic Pi oferece uma solução para este problema com as funções cuee sync.

cuepermite-nos enviar uma mensagem de “batimento cardiaco” para todos os outros threads. Por padrão os outro threads não estão interessados na mensagem. No entanto, podes registar interesse com a função sync.

A coisa importante a saber é que sync é similar a sleepna medida que pára o thread corrente de fazer alguma coisa por um período de tempo. No entanto, com sleepespecificas o tempo de espera enquanto com syncnão sabes quanto tempo tens de esperar - uma vez que syncespera pelo próximo cuede outro thread que pode ser logo ou após um longo tempo.

Vamos explorar isto em mais detalhe:

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

Aqui temos 2 threads - um actuando como um metronomo, não tocando nenhum som mas enviando a mensagem de batimento:tick a cada beat. O segundo thread está sincronizado na mensagem tick e quando recebe um herda o tempo do thread do cue e continua a correr.

Como resultado, ouviremos o sample :drum_heavy_kick exactamente quando o outro thread envia a mensagem :tick, mesmo que o dois threads não começaram o seu tempo de execução ao mesmo tempo:

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

A chamada maldosa sleeptipicamente faria o segundo thread fora de fase com o primeiro. No entanto, como estamos a usar cue e sync, automaticamente sincronizamos os threads evitando qualquer offset de tempo acidental.

Nomes de Cues

És livre de usar qualquer nome que queiras para as tuas mensagens de cue- não apenas :tick . Só tens que assegurar que tal como os outros threads estão syncronizados no nome correcto - ou então ficarão à espera para sempre (ou até carregares no botão Stop).

Vamos jogar com alguns nomes de cues:

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

Aqui temos um cue loop principal que está aleatoriamente a enviar um batimento chamado :foo,:bar ou :baz. Temos também 3 loop threads sincronizando a cada um desses nomes independentemente e tocando um sample diferente. O efeito total é que ouvimos um som cada 0.5 batida porque cada syncthread é aleatoriamente sincronizado com a thread cue e toca o seu sample.

Isto claro que também funciona se inverteres a ordem dos threads pois eles os threads sync simplesmente esperam pela próxima cue.