Как сделать семафор

Обновлено: 05.07.2024

В C++20 в стандартной библиотеке появились семафоры.

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

Семафоры могут быть двоичными и вычислительными. Вычислительные семафоры могут принимать целочисленные неотрицательные значения и используются для работы с ресурсами, количество которых ограничено, либо участвуют в синхронизации параллельно исполняемых задач. Двоичные семафоры могут принимать только значения 0 и 1 и используются для взаимного исключения одновременного нахождения двух или более процессов в своих критических секциях.

Мьютексные семафоры (мьютексы) являются упрощённой реализацией семафоров, аналогичной двоичным семафорам с тем отличием, что мьютексы должны отпускаться тем же потоком, который осуществляет их захват. Мьютексы наряду с двоичными семафорами используются в организации критических участков кода. В отличие от двоичных семафоров, начальное состояние мьютекса не может быть захваченным.

Стандартная библиотека C++ предлагает к использованию вычислительные и двоичные семафоры, представленные классами std::counting_semaphore и std::binary_semaphore .

counting_semaphore– это примитив синхронизации, который может управлять доступом к общему ресурсу. В отличие от мьютекса std::mutex , counting_semaphore допускает более одного параллельного доступа к одному и тому же ресурсу.

counting_semaphore содержит внутренний счетчик, который инициализируется конструктором. Этот счетчик уменьшается при вызовах метода acquire() и связанных с ним методов и увеличивается при вызовах метода release() . Когда счетчик равен нулю, acquire() блокирует поток до тех пор, пока счетчик не увеличится. Кроме того, для использования доступны методы:

  • try_acquire() не блокирует поток, а возвращает вместо этого false . Подобно std::condition_variable::wait() , метод try_acquire() может ошибочно возвращать false .
  • try_acquire_for() и try_acquire_until() блокируют до тех пор, пока счетчик не увеличится или не будет достигнут таймаут.

Семафоры нельзя копировать и перемещать.

В отличие от std::mutex , counting_semaphore не привязан к потокам выполнения – освобождение release() и захват acquire() семафора могут производиться в разных потоках (блокировка и освобождение мьютекса должны производиться одним и тем же потоком). Все операции над counting_semaphore могут выполняться одновременно и без какой-либо связи с конкретными потоками выполнения.

Семафоры также часто используются для реализации уведомлений. При этом семафор инициализируется значением 0, и потоки, ожидающие события блокируются методом acquire() , пока уведомляющий поток не вызовет release(n) . В этом отношении семафоры можно рассматривать как альтернативу std::condition_variable .

Методы acquire() уменьшают значение счётчика семафора на 1. Методу release() можно передать в качестве параметра значение, на которое должен быть увеличен счётчик, по умолчанию значение равно 1.

std::counting_semaphore является шаблонным классом. В качестве параметра шаблона принимает значение, которое является нижней границей для максимально возможного значения счётчика. Фактический же максимум значений счётчика определяется реализацией и может быть больше, чем LeastMaxValue .

binary_semaphore – это просто псевдоним using binary_semaphore = std::counting_semaphore ; .

Данил Золин

Данил Золин запись закреплена

Роман Танаев

Данил Золин

Андрей Платонов

Данил, чертим детали. Берем листовой металл, режем заготовки, сгинаем мачту светофора, паяем детали. Устанавливаем лампоччки/светодиоды. Подпаиваем к лампам провода, протаскиваем их через мачту. Красим светофор. Делов-то.

Я пытаюсь воссоздать библиотеку "Blackbox". В моем классе CS, когда мы должны использовать семафоры (в нашем финале на бумаге), мы получаем файл "sem.h". Есть 3 функции: одна для создания нового семафора с начальным числом токенов, одна для извлечения токена из семафора и одна для помещения токена в семафор. при 0 токены любой поток, использующий блокирующую функцию, должен ждать токен.

Для лучшего понимания я пытался воссоздать этот sem.h и sem.c, основываясь на некоторых экзаменах с просьбой о реализации отдельных функций. поскольку все это делается на бумаге, компиляция не нужна, но я чувствую, что я близок

Кроме того, неясно, для чего это нужно: функции должны ограничивать количество потоков, которые могут получить доступ к сегменту кода одновременно.

Как только 1-й поток пропустит P (), любые последующие вызовы P не смогут пройти до тех пор, пока V не будет вызван в том же sem

При компиляции я получаю следующую ошибку:

2 ответа

Вы не хотите статическую переменную. Вы хотите создать новый объект (выделение памяти) каждый раз, когда вызывается semCreate . В качестве таких,

Не забудьте освободить семафор, когда закончите с ним. Это включает в себя по ошибке!

Кроме того, у вашего кода есть несколько проблем. Короче говоря, P совершенно не прав.

  • P может вызывать pthread_cond_signal с разблокированным мьютексом.
  • P может вернуться с заблокированным мьютексом.
  • P уменьшает значение, когда оно не положительно, когда предполагается уменьшить его, когда оно положительно.

И есть проблема в том, что и P , и V выполняют бессмысленную и даже вредную обработку ошибок. Пропустить разблокировку мьютекса, если трансляция не удалась? Да, не будем этого делать.

Давайте начнем с нуля с базового решения, безотносительно к безопасности и эффективности.

Теперь давайте сделаем его потокобезопасным через взаимное исключение.

Но это напряженное ожидание. Давайте использовать условие var для сна, пока семафор не изменится. (Помните, что cond_wait разблокирует предоставленный мьютекс при входе и блокирует его перед возвратом.)

  • Нет смысла вызывать pthread_cond_broadcast , поскольку только один поток может изменять семафор одновременно. Используя V и P вызов pthread_cond_signal в случае необходимости, мы избегаем бесполезного пробуждения потоков.
  • Мы можем не проверять, если pthread_mutex_lock , pthread_mutex_unlock и pthread_cond_signal не работают в рабочем коде, так как они приводят только к ошибке кодирования.

Ваш Procure не так. Внутри цикла вы отпускаете мьютекс; что делает недействительным как тест в верхней части цикла, так и ожидание cond.

Вы, вероятно, хотите что-то вроде:

Пробуждение из условной переменной - это просто указание на то, что вам следует перепроверить свое состояние; не утверждение, что ваше состояние наступило.

В двух предыдущих статья мы изучили основы работы с FreeRTOS в Arduino, а также работу в ней с очередями. В этой же статье мы еще больше углубимся в изучение FreeRTOS в Arduino и рассмотрим такие понятия как семафоры и мьютексы.

Семафоры и мьютексы в операционных системах (ОС) реального времени являются объектами ядра, которые используются для синхронизации, управления ресурсами и защиты ресурсов от повреждения. Соответственно, в первой части статьи мы рассмотрим работу с семафорами, а во второй – с мьютексами.

Использование семафоров и мьютексов в FreeRTOS на Arduino Uno

Что такое семафор (Semaphore)

В статье по основам FreeRTOS в Arduino мы изучили, что у всех задач есть их приоритеты и когда задача более высокого приоритета прерывает выполнение задачи более низкого приоритета, то это может привести к потере/повреждению данных задачи более низкого приоритета поскольку задача не будет выполняться, а данные для нее в это время могут продолжать поступать (например, от какого-нибудь датчика). В результате мы получим неудовлетворительную работу всего приложения.

Для защиты ресурсов от подобных потерь важную роль играют семафоры.

Семафор представляет собой сигнальный механизм, в котором задача, находящаяся в режиме ожидания (waiting state), сигналит об этом другой задаче. К примеру, если задача task1 заканчивает свою работу, она передает флаг или инкремент флага на 1 и когда этот флаг принимается другой задачей (task2), это говорит ей о том, что теперь она может выполнять свою работу. Когда задача task2 заканчивает свою работу значение флага уменьшается на 1.

Таким образом, мы имеем дело с механизмом "передать" и "принять" (“Give” and “Take” mechanism), а семафор является переменной целого типа, которая используется для синхронизации доступа к ресурсам.

В FreeRTOS возможны семафоры двух типов:

  1. Бинарные семафоры (Binary Semaphore).
  2. Счетные семафоры (Counting Semaphore).

1. Бинарные семафоры – могут принимать значения 0 и 1. В некотором смысле такой семафор похож на очередь длины 1. К примеру, у нас есть две задачи - task1 и task2. Task1 передает данные задаче task2, при этом задача task2 непрерывно проверяет равен ли элемент очереди 1. Если он равен 1, то task2 может считывать данные, иначе должна подождать до тех пор пока он не равен 1. После получения данных task2 декрементирует (уменьшает на 1) элемент очереди, таким образом, он становится равным 0. Это будет означать что задача task1 снова может передавать данные задаче task2.

Исходя из представленного примера можно сказать что бинарный семафор используется для синхронизации между задачами или для синхронизации между задачей и прерыванием.

2. Счетный семафор – может принимать не только значения равные 0 и 1, поэтому он эквивалентен очереди с длиной больше 1 элемента. Этот вид семафоров используется для событий счета. В данном случае обработчик события будет "передавать" семафор каждый раз когда происходит событие (инкрементирование значения счета семафора), а обработчик задачи будет "принимать" семафор каждый раз когда он обрабатывает событие (декрементирование значения счета семафора).

В данном случае значение счета, следовательно, будет равно разнице между числом событий, которые случились (произошли), и числом событий, которые были обработаны.

Кратко теорию по семафорам мы рассмотрели, теперь посмотрим каким образом их можно использовать в FreeRTOS.

Как использовать семафоры в FreeRTOS

FreeRTOS поддерживает различные API (application programming interface - программный интерфейс приложения) функции для создания семафоров, их передачи и приема.

Можно использовать два типа API функций для одного и того же объекта ядра. Если нам нужно передать семафор из подпрограммы обработки прерывания (ISR), то в этом случае нельзя использовать API для обычного семафора. В данном случае необходимо использовать защищенное от прерываний API.

В данной статье мы рассмотрим использование только бинарных семафоров поскольку они проще для понимания. Поскольку в данном проекте мы будем использовать прерывания, то нам необходимо будет использовать защищенное от прерываний API в функции обработки прерывания (ISR function). При этом когда мы будем говорить о синхронизации между задачей и прерыванием, это будет означать перевод задачи в работающее состояние (Running state) после выполнения функции обработки прерывания (ISR function).

Создание семафора

Чтобы использовать любой объект ядра его сначала необходимо создать. Для создания бинарного семафора мы будем использовать функцию vSemaphoreCreateBinary() .

Эта API функция не требует никаких параметров и возвращает переменную типа SemaphoreHandle_t . Для хранения значения семафора мы создадим глобальную переменную sema_v .

Читайте также: