5.7 Synchronisation des threads

# Synchronisation des threads

Une fois que vous êtes suffisamment avancés en codage "live" avec un certain nombre de fonctions et de threads en simultanéité, vous avez probablement remarqué que c'est plutôt facile de faire une faute dans l'un des threads et qui lui met fin. Ce n'est pas un gros problème car vous pouvez facilement redémarrer le thread en pressant Run. Toutefois, quand vous redémarrez le thread, il se trouve alors *déphasé* avec les threads d'origine.

## Temps hérité

Comme nous en avons discuté auparavant, les nouveaux threads créés avec `in_thread` héritent de tous les paramètres du thread parent. Ceci inclut l'heure actuelle. Ce qui signifie que les threads sont toujours en phase entre eux quand ils sont démarrés simultanément.

Toutefois quand vous démarrez un thread tout seul, il démarre avec sa propre heure qui n'est probablement en phase avec aucun des autres threads en cours d'exécution.

## Cue et sync

Sonic Pi fournit une solution à ce problème avec les fonctions `cue` et `sync`.

`cue` nous permet d'envoyer nos messages de battement de cœur à tous les autres threads. Par défaut, les autres threads ne sont pas intéressés et ignorent ces messages de battement de coeur. Cependant, vous pouvez leur déclarer de l'intérêt avec la fonction `sync`.

La chose importante à laquelle il faut être attentif est que `sync` est similaire à `sleep` dans le sens qu'elle arrête le thread courant et l'empêche de faire quoi que ce soit pendant un moment. Toutefois, avec `sleep`, vous spécifiez combien de temps vous voulez attendre tandis qu'avec `sync`, vous ne savez pas combien de temps vous allez attendre - étant donné que `sync` attend le `cue` suivant d'un autre thread, ce qui peut être court ou long.

Explorons ceci en un peu plus de détail :

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

Nous avons ici deux threads - l'un agissant comme un métronome, ne jouant aucun son mais envoyant des messages de battement de cœur `:tick` à chaque temps. Le second thread se synchronise sur les messages `tick` et quand il en reçoit un, il hérite de l'heure du thread `cue` et continue de jouer.

Par conséquent, nous entendons l'échantillon `:drum_heavy_kick` exactement quand l'autre thread envoie le message `:tick`, même si les deux threads n'ont pas démarré leur exécution en même temps :

```
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
```

Ce vilain appel à `sleep` mettrait typiquement le second thread en déphasage avec le premier. Cependant, comme nous utilisons `cue` et `sync`, nous synchronisons automatiquement les threads en évitant un décalage de temps accidentel.

## Noms des "cue"

Vous êtes libre d'utiliser n'importe quel nom que vous aimeriez pour vos messages `cue`, pas seulement `:tick`. Vous devez juste vous assurer que tout autre thread se synchronise sur le bon nom - autrement il attendrait à jamais (au moins jusqu'à ce que vous pressiez le bouton Stop).

Jouons avec quelques noms de `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
```

Ici, nous avons une boucle principale `cue` qui envoie aléatoirement un des battements de cœur nommés `:foo`, `:bar` ou `:baz`. Nous avons aussi ensuite trois boucles en thread se synchronisant sur ces noms indépendamment et jouant un échantillon différent. Il est clair que nous entendons un son à chaque demi-temps puisque chacun des threads `sync` est aléatoirement synchronisé avec le thread `cue` et joue son échantillon.

Ceci, bien entendu, marche aussi si vous ordonnez les threads en sens inverse puisque les threads `sync` restent en attente du `cue` suivant.
