# Синхронизация потоков

Когда у вас начнёт достаточно хорошо получаться лайвкодинг с большим количеством потоков, выполняющихся одновременно, вы, скорее всего, заметите, что очень легко допустить ошибку, которая может прервать поток. Ничего страшного в этом нет, так как поток может быть перезапущен простым нажатием кнопки “Выполнить”. Однако, после перезапуска потока он будет играть невпопад с другими.

## Унаследованное время

Как мы обсуждали ранее, новые потоки, созданные при помощи `in_thread` наследуют все настройки от родительского потока. Они включают и текущую метку времени. Это означает, что потоки всегда синхронизированы друг с другом после одновременного их запуска.

Но, когда поток стартует отдельно от родительского, внутри него ведётся собственный отсчет времени, который вряд ли совпадёт с каким-либо из других активных потоков.

## Функции cue и sync

В Sonic Pi решением этой проблемы являются функции `cue` и `sync`.

`cue` позволяет нам отправлять сигнал пульса другим потокам. По умолчанию, другие потоки не слушают и пропускают эти сообщения. Однако, поток может легко заявить о своей заинтересованности, вызвав функцию `sync`.

Важно осознавать, что функция `sync` похожа на `sleep`, ведь она останавливает текущий поток, и тот некоторое время не выполняется. Для `sleep` время простоя указывается явно, но вызвав `sync`, вы не знаете сколько времени оно продлится, потому что `sync` ждёт следующий `cue` от другого потока. Это может случиться скоро, а может и нет.

Давайте выясним немного больше деталей:

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

Здесь у нас есть два потока. Один работает как метроном. Он не играет никаких звуков, только отправляет `:tick` сигнал каждую долю такта. Второй поток синхронизуется с сообщениями `tick`. Когда он получает `:tick` сообщение, то наследует временную отметку потока, вызвавшего `cue`, и продолжает выполнение.

В результате мы будем слышать сэмпл `:drum_heavy_kick` в тот самый момент, когда другой поток отправляет `:tick` сигнал. Даже если два этих потока стартовали не одновременно, это всё равно будет происходить:

    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

Результатом вызова `sleep` будет расхождение второго потока с первым. Однако, так как мы используем `cue` и `sync`, то мы автоматически синхронизируем потоки и избегаем любых случайных временных сдвигов.

## Имена сигналов синхронизации

Для сигналов `cue` можно использовать любые названия, а не только `:tick`. Просто следите за тем, чтобы все остальные потоки вызывали `sync` с правильным именем. Иначе они остановятся навсегда (или, по крайней мере, пока вы не нажмёте кнопку “Остановить”).

Попробуем запрограммировать что-нибудь с использованием нескольких имен `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

Тут у нас есть цикл с функцией `cue`, отправляющей пульс. Случайным образом этот пульс может быть назван `:foo`, `:bar` или `:baz`. Ещё есть три цикла в потоках, которые синхронизируются с каждым из этих сигналов независимо и воспроизводят разные сэмплы. Чистый эффект от этого кода в том, что каждые полсекунды мы слышим звук, так как каждый из потоков `sync` случайным образом синхронизируется с потоком `cue` и проигрывает свой сэмпл.

Конечно же, код будет работать даже если расположить потоки в обратном порядке, поскольку потоки будут дожидаться следующего `cue`.