Всем привет! Сегодня хочу углубиться в одну из самых важных тем в мире Zephyr OS — синхронизацию потоков и задач. Если вы хоть раз работали с k_thread_create, гоняли потоки туда-сюда и ловили bus fault, вы меня понимаете.

Когда задача одна — всё просто. Но как только вырастает многопоточность, задачи начинают драться за общий ресурс — и тут без надёжной синхронизации не обойтись. У Zephyr OS есть три главных инструмента для этого: spinlock, mutex и semaphore. Каждый со своим характером и подводными камнями.


? Зачем всё это нужно?

Когда в коде несколько потоков, они часто делят общие ресурсы: память, шину, датчики, регистры. Если два потока попытаются одновременно писать в одну переменную или управлять одним и тем же периферийным устройством — всё, привет баги и падения.
Синхронизация — это гарант, что один поток «держит» ресурс, а остальные ждут своей очереди.


⚡ Spinlock: всё или ничего

Что такое spinlock?

Spinlock — самая «грубая» блокировка. Когда поток не может получить доступ к ресурсу, он просто крутится в цикле и ждёт, пока другой его отпустит. Активное ожидание = CPU занят на 100%.

Где это работает?

  • Короткие операции — десятки/сотни микросекунд.

  • ISR (Interrupt Service Routine) — например, в драйверах для работы с железом на уровне регистров.

  • Быстрая блокировка без переключения контекста — никаких «уснул-проснулся».

Что может пойти не так?

  • Если вдруг операция окажется не такой быстрой — поток будет зря жечь процессор.

  • На однопоточной системе это вообще может остановить всё.

  • Рекурсия или забыли освободить блокировку — ловим deadlock.

Мой опыт: Spinlock я обычно беру для атомарных действий: прочитать-регистр-записать, быстро передать флаг из прерывания. Но если вижу, что время блокировки растёт — сразу переключаюсь на mutex.


? Mutex: спи спокойно, поток

Что такое mutex?

Mutex (mutual exclusion) — классическая блокировка с «сонным режимом». Если ресурс занят — поток засыпает и освобождает CPU для других задач. Когда ресурс освобождается, «просыпается» тот, кто ждал дольше всех.

Плюсы mutex:

  • Экономия CPU: нет кручения холостого цикла.

  • Есть priority inheritance — если поток с низким приоритетом держит ресурс, а высокоприоритетный ждёт — Zephyr может временно поднять приоритет владельца. Так мы избегаем priority inversion.

  • Идеален для долгих операций: запись файла, обработка сложных данных.

Минусы mutex:

  • Переключение контекста всегда стоит времени.

  • В ISR mutex использовать нельзя — спать там нельзя по определению.

  • Есть риск deadlock, если не продумать порядок блокировок.

Мой опыт: Для многопоточных систем mutex — мой главный инструмент. Если поток работает с глобальными структурами или с железом, к которому можно лезть минутами — только mutex. Главное — следить за тем, чтобы не держать его слишком долго.


? Semaphore: больше, чем просто замок

Что такое semaphore?
Semaphore — универсальный инструмент. Можно думать о нём как о счётчике сигналов или ключей доступа. Если у тебя бинарный семафор — он работает почти как mutex: заблокировал/освободил. Но, в отличие от mutex, у семафора нет понятия владельца.

Счётный семафор — это прям клад для многозадачности. Можно ограничивать, сколько потоков одновременно выполняет какую-то работу. Например, есть пул из 3 UART портов — запустили три задачи, четвёртая ждёт, пока один UART освободится.

Семафор умеет ещё и другое — сигнализировать события:

  • ISR может кинуть сигнал, что данные готовы.

  • Поток подхватывает семафор и начинает обработку.

Плюсы семафора:

  • Гибкость: можно использовать и для блокировок, и для сигналов.

  • Подходит для взаимодействия ISR и потоков.

Минусы семафора:

  • Если перепутать логику — счётчик может уйти в минус или переполниться.

  • Нет встроенного механизма приоритетов.

  • Лёгко запутаться, кто когда должен дать или взять.

Мой опыт: Семафор — моя любимая штука для «оживления» потоков. Например, когда очередь задач пула потоков заполняется — ISR даёт сигнал семафором, поток просыпается и берёт новую задачу. Всё чётко и без лишнего кода.


? Как выбрать правильно?

Вот как я это вижу после множества проектов:

Инструмент

Где применять

Чего избегать

Spinlock

Суперкороткие, критические операции. Драйверы, ISR.

Долгие блокировки, однопоточные системы.

Mutex

Долгие операции с общим ресурсом. Сложная многопоточность.

ISR, неоптимальные долгие блокировки.

Semaphore

Сигналы событий, управление количеством потоков, ISR → поток.

Путаница со счётчиком, отсутствие строгой «владельческой» модели.


⚙️ Личный чеклист

  • Действие быстрее 1-10 мкс? Spinlock.

  • Нужно надолго занять ресурс? Mutex.

  • Нужно разбудить поток или ограничить число задач? Semaphore.


? Итог

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

Освойте эти три инструмента, и управление многозадачностью в Zephyr OS перестанет быть кошмаром.

Если интересно, могу поделиться рабочими примерами кода или своими шаблонами — напишите в комментах! Разберём всё до винтика.

Комментарии (1)