Что делает семафор?
Семафор – один из старейших примитивов синхронизации. Он был изобретен Дейкстрой в 1968 году. По большому счету это счетчик, который можно увеличивать и уменьшать из разных потоков. Уменьшение до 0 блокирует уменьшающий поток. Состояние, когда счетчик больше нуля называют сигнальное состояние, операцию его увеличения – release (освобождение) или signal, уменьшения – acquire (захват) или wait.
На практике можно представить, что release – выделение квоты доступа к критической секции программы. acquire – использование необходимого объема доступной квоты, или ожидание, если её не хватает. Подробнее с деталями работы семафора поможет ознакомиться перевод статьи с картинками на хабре.
В Java семафор реализован классом Semaphore . Состоит этот класс в основном из разных форм методов acquire (с таймаутом, с игнорированием InterruptedException , неблокирующий) и release . Методы могут принимать параметр permits – тот самый объем квот, которые необходимо освободить/захватить.
Несколько вспомогательных методов позволяют узнать больше о количестве и составе очереди потоков, которые ждут освобождения пермитов. А методы availablePermits и drainPermits позволяют узнать количество оставшихся пермитов, и захватить их все соответственно. В конструкторе конфигурируются изначальное количество пермитов, и свойство fair (аналогичное свойству ReentrantLock).
Что такое семафор java
Семафоры представляют еще одно средство синхронизации для доступа к ресурсу. В Java семафоры представлены классом Semaphore , который располагается в пакете java.util.concurrent .
Для управления доступом к ресурсу семафор использует счетчик, представляющий количество разрешений. Если значение счетчика больше нуля, то поток получает доступ к ресурсу, при этом счетчик уменьшается на единицу. После окончания работы с ресурсом поток освобождает семафор, и счетчик увеличивается на единицу. Если же счетчик равен нулю, то поток блокируется и ждет, пока не получит разрешение от семафора.
Установить количество разрешений для доступа к ресурсу можно с помощью конструкторов класса Semaphore:
Semaphore(int permits) Semaphore(int permits, boolean fair)
Параметр permits указывает на количество допустимых разрешений для доступа к ресурсу. Параметр fair во втором конструкторе позволяет установить очередность получения доступа. Если он равен true , то разрешения будут предоставляться ожидающим потокам в том порядке, в каком они запрашивали доступ. Если же он равен false , то разрешения будут предоставляться в неопределенном порядке.
Для получения разрешения у семафора надо вызвать метод acquire() , который имеет две формы:
void acquire() throws InterruptedException void acquire(int permits) throws InterruptedException
Для получения одного разрешения применяется первый вариант, а для получения нескольких разрешений — второй вариант.
После вызова этого метода пока поток не получит разрешение, он блокируется.
После окончания работы с ресурсом полученное ранее разрешение надо освободить с помощью метода release() :
void release() void release(int permits)
Первый вариант метода освобождает одно разрешение, а второй вариант — количество разрешений, указанных в permits.
Используем семафор в простом примере:
import java.util.concurrent.Semaphore; public class Program < public static void main(String[] args) < Semaphore sem = new Semaphore(1); // 1 разрешение CommonResource res = new CommonResource(); new Thread(new CountThread(res, sem, "CountThread 1")).start(); new Thread(new CountThread(res, sem, "CountThread 2")).start(); new Thread(new CountThread(res, sem, "CountThread 3")).start(); >> class CommonResource < int x=0; >class CountThread implements Runnable < CommonResource res; Semaphore sem; String name; CountThread(CommonResource res, Semaphore sem, String name)< this.res=res; this.sem=sem; this.name=name; >public void run() < try< System.out.println(name + " ожидает разрешение"); sem.acquire(); res.x=1; for (int i = 1; i < 5; i++)< System.out.println(this.name + ": " + res.x); res.x++; Thread.sleep(100); >> catch(InterruptedException e) System.out.println(name + " освобождает разрешение"); sem.release(); > >
Итак, здесь есть общий ресурс CommonResource с полем x, которое изменяется каждым потоком. Потоки представлены классом CountThread, который получает семафор и выполняет некоторые действия над ресурсом. В основном классе программы эти потоки запускаются. В итоге мы получим следующий вывод:
CountThread 1 ожидает разрешение CountThread 2 ожидает разрешение CountThread 3 ожидает разрешение CountThread 1: 1 CountThread 1: 2 CountThread 1: 3 CountThread 1: 4 CountThread 1 освобождает разрешение CountThread 3: 1 CountThread 3: 2 CountThread 3: 3 CountThread 3: 4 CountThread 3 освобождает разрешение CountThread 2: 1 CountThread 2: 2 CountThread 2: 3 CountThread 2: 4 CountThread 2 освобождает разрешение
Семафоры отлично подходят для решения задач, где надо ограничивать доступ. Например, классическая задача про обедающих философов. Ее суть: есть несколько философов, допустим, пять, но одновременно за столом могут сидеть не более двух. И надо, чтобы все философы пообедали, но при этом не возникло взаимоблокировки философами друг друга в борьбе за тарелку и вилку:
import java.util.concurrent.Semaphore; public class Program < public static void main(String[] args) < Semaphore sem = new Semaphore(2); for(int i=1;i<6;i++) new Philosopher(sem,i).start(); >> // класс философа class Philosopher extends Thread < Semaphore sem; // семафор. ограничивающий число философов // кол-во приемов пищи int num = 0; // условный номер философа int id; // в качестве параметров конструктора передаем идентификатор философа и семафор Philosopher(Semaphore sem, int id) < this.sem=sem; this.id=id; >public void run() < try < while(num<3)// пока количество приемов пищи не достигнет 3 < //Запрашиваем у семафора разрешение на выполнение sem.acquire(); System.out.println ("Философ " + id+" садится за стол"); // философ ест sleep(500); num++; System.out.println ("Философ " + id+" выходит из-за стола"); sem.release(); // философ гуляет sleep(500); >> catch(InterruptedException e) < System.out.println ("у философа " + id + " проблемы со здоровьем"); >> >
В итоге только два философа смогут одновременно находиться за столом, а другие будут ждать:
Философ 1 садится за стол Философ 3 садится за стол Философ 3 выходит из-за стола Философ 1 выходит из-за стола Философ 2 садится за стол Философ 4 садится за стол Философ 2 выходит из-за стола Философ 4 выходит из-за стола Философ 5 садится за стол Философ 1 садится за стол Философ 1 выходит из-за стола Философ 5 выходит из-за стола Философ 3 садится за стол Философ 2 садится за стол Философ 3 выходит из-за стола Философ 4 садится за стол Философ 2 выходит из-за стола Философ 5 садится за стол Философ 4 выходит из-за стола Философ 5 выходит из-за стола Философ 1 садится за стол Философ 3 садится за стол Философ 1 выходит из-за стола Философ 2 садится за стол Философ 3 выходит из-за стола Философ 5 садится за стол Философ 2 выходит из-за стола Философ 4 садится за стол Философ 5 выходит из-за стола Философ 4 выходит из-за стола
Java Dev Notes
Семафор используется для обмена сигналами между потоками, или же для охраны критической секции. Их также можно использовать и вместо локов. Несмотря на то, что в JDK уже реализован семафор (java.util.concurrent.Semaphore), полезно будет самим реализовать этот объект.
Итак, у нашего семафора будет всего лишь два метода: take, release . Соответственно, простейшая реализация будет такой:
public class SimpleSemaphore < boolean taken = false; public synchronized void take() < this.taken = true; this.notify(); >public synchronized void release() throws InterruptedException < while (!this.taken) wait(); this.taken = false; >>
Теперь давайте рассмотрим программу, которая использует семафор для обмена сигналами. У нас будет два потока: SignalSender, SignalReceiver , которые будут посылать друг другу сигналы. Вот код:
public class Main < public static void main(String[] args) throws InterruptedException < SimpleSemaphore semaphore = new SimpleSemaphore(); new Thread(new SignalSender(semaphore)).start(); Thread.currentThread().sleep(2000); new Thread(new SignalReceiver(semaphore)).start(); >static class SignalSender implements Runnable < private final SimpleSemaphore semaphore; public SignalSender(SimpleSemaphore semaphore) < this.semaphore = semaphore; >@Override public void run() < System.out.println("[SignalSender] run"); while (true) < try < doSomeWork(); semaphore.take(); >catch (InterruptedException e) < e.printStackTrace(); >> > private void doSomeWork() throws InterruptedException < System.out.println("[SignalSender] do some work"); Thread.sleep(500); >> static class SignalReceiver implements Runnable < private final SimpleSemaphore semaphore; public SignalReceiver(SimpleSemaphore semaphore) < this.semaphore = semaphore; >@Override public void run() < System.out.println("[SignalReceiver] run"); while (true) < try < semaphore.release(); doSomeWork(); >catch (InterruptedException e) < e.printStackTrace(); >> > private void doSomeWork() throws InterruptedException < System.out.println("[SignalReceiver] do some work"); Thread.sleep(700); >> >
Посмотрим на вывод консоли:
[SignalSender] run [SignalSender] do some work [SignalSender] do some work [SignalSender] do some work [SignalSender] do some work [SignalSender] do some work [SignalReceiver] run [SignalReceiver] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalReceiver] do some work [SignalSender] do some work [SignalSender] do some work [SignalReceiver] do some work
Мы видим, как потоки обмениваются сигналами, при этом они ждут друг друга, и начинают работу, только получив соответствующий сигнал от семафора.
Что такое Семафор? Подсчет, двоичные типы с примером
семафор — это просто переменная, которая неотрицательна и распределяется между потоками. Семафор — это сигнальный механизм, и поток, ожидающий семафора, может получить сигнал от другого потока. Он использует две атомарные операции: 1) Ожидание и 2) Сигнал для синхронизации процесса.
Семафор либо разрешает, либо запрещает доступ к ресурсу, в зависимости от того, как он настроен.
Характеристика семафора
Вот характеристики семафора:
- Это механизм, который можно использовать для синхронизации задач.
- Это механизм синхронизации низкого уровня.
- Семафор всегда будет содержать неотрицательное целое значение.
- Семафор можно реализовать с помощью тестовых операций и прерываний, которые должны выполняться с использованием файловых дескрипторов.
Типы семафоров
Два распространенных типа семафоров:
- Подсчет семафоров
- Бинарные семафоры.
Подсчет семафоров
Этот тип семафора использует счетчик, который помогает задаче получать или освобождать множество раз. Если начальный счетчик = 0, счетный семафор должен быть создан в недоступном состоянии.
Однако если счетчик > 0, семафор создается в доступном состоянии, и количество имеющихся у него токенов равно его счетчику.
Двоичные семафоры
Бинарные семафоры очень похожи на счетные семафоры, но их значения ограничены 0 и 1. В этом типе семафоров операция ожидания работает только в том случае, если семафор = 1, а операция сигнала завершается успешно, когда семафор = 0. Это легко реализовать, чем подсчет семафоров.
Пример семафора
Приведенная ниже программа представляет собой пошаговую реализацию, которая включает в себя использование и объявление семафора.
Shared var mutex: semaphore = 1; Process i begin . . P(mutex); execute CS; V(mutex); . . End;
Ожидание и сигнальные операции в семафорах
Обе эти операции используются для реализации синхронизация процессов. Цель этой операции с семафором — добиться взаимного исключения.
Подождите операции
Этот тип операции семафора помогает контролировать попадание задачи в критическую секцию. Однако если значение ожидания положительное, значение аргумента ожидания X уменьшается. В случае отрицательного или нулевого значения никакая операция не выполняется. Ее также называют операцией P(S).
После уменьшения значения семафора, которое становится отрицательным, команда удерживается до тех пор, пока не будут выполнены необходимые условия.
Copy CodeP(S)
Сигнальная операция
Этот тип операции Семафора используется для контроля выхода задачи из критической секции. Это помогает увеличить значение аргумента на 1, что обозначается как V(S).
Copy CodeP(S) < while (S>=0); S++; >
Счетный семафор против двоичного семафора
Вот некоторые основные различия между счетным и двоичным семафором:
Подсчет семафоров | Двоичный семафор |
---|---|
Нет взаимного исключения | Взаимное исключение |
Любое целочисленное значение | Значение только 0 и 1 |
Более одного слота | Только один слот |
Предоставьте набор процессов | Имеет механизм взаимного исключения. |
Разница между семафором и мьютексом
параметры | семафор | Mutex |
---|---|---|
Механизм | Это тип сигнального механизма. | Это запорный механизм. |
Тип данных | Семафор — целочисленная переменная. | Мьютекс — это просто объект. |
Модификация | Операции ожидания и сигнала могут изменить семафор. | Он изменяется только процессом, который может запросить или освободить ресурс. |
Управление ресурсами | Если ни один ресурс не свободен, процессу требуется ресурс, который должен выполнить операцию ожидания. Он должен подождать, пока счетчик семафора не станет больше 0. | Если он заблокирован, процесс должен подождать. Процесс должен храниться в очереди. Доступ к нему необходим только тогда, когда мьютекс разблокирован. |
Нить | Вы можете иметь несколько потоков программы. | Вы можете иметь несколько потоков программы во мьютексе, но не одновременно. |
Собственность | Значение может быть изменено любым процессом, высвобождающим или получающим ресурс. | Блокировка объекта снимается только тем процессом, который получил его блокировку. |
Тип | Типы семафоров: счетный семафор, двоичный семафор и | Мьютекс не имеет подтипов. |
Эксплуатация | Значение семафора изменяется с помощью операций ожидания () и сигнала (). | Объект-мьютекс заблокирован или разблокирован. |
Занятость ресурсов | Он занят, если все ресурсы используются и процесс, запрашивающий ресурс, выполняет операцию wait() и блокируется до тех пор, пока счетчик семафоров не станет >1. | В случае, если объект уже заблокирован, процесс, запрашивающий ресурсы, ожидает и ставится в очередь системы, прежде чем блокировка будет снята. |
Преимущества семафоров
Вот плюсы/преимущества использования семафора:
- Это позволяет более чем одному потоку получить доступ к критической секции.
- Семафоры машинно-независимы.
- Семафоры реализованы в машинно-независимом коде микроядра.
- Они не позволяют нескольким процессам войти в критическую секцию.
- Поскольку в семафоре имеется занятое ожидание, никогда не происходит потери времени и ресурсов процесса.
- Они машинно-независимы и должны выполняться в машинно-независимом коде микроядра.
- Они позволяют гибко управлять ресурсами.
Недостаток семафоров
Вот минусы/недостаток семафора
- Одним из самых больших ограничений семафора является инверсия приоритета.
- Операционная система должна отслеживать все вызовы ожидания и сигнализировать семафор.
- Их использование никогда не является обязательным, но только по соглашению.
- Чтобы избежать взаимоблокировок в семафоре, операции ожидания и сигнала должны выполняться в правильном порядке.
- Программирование семафоров является сложным, поэтому есть вероятность не добиться взаимного исключения.
- Это также непрактичный метод для крупномасштабного использования, поскольку их использование приводит к потере модульности.
- Семафор более подвержен ошибкам программиста.
- Это может вызвать тупик или нарушение взаимного исключения из-за ошибки программиста.
Итоги
- Семафор определяется как неотрицательная переменная, разделяемая между потоками.
- Это механизм, который можно использовать для синхронизации задач.
- Счетный семафор использует счетчик, который помогает задаче быть полученной или освобожденной несколько раз.
- Бинарные семафоры очень похожи на счетные семафоры, но их значение ограничено 0 и 1.
- Операция ожидания помогает контролировать попадание задачи в критическую секцию.
- Работа сигнального семафора используется для контроля выхода задачи из критического участка.
- Счетный семафор не имеет взаимного исключения, тогда как двоичный семафор имеет взаимное исключение.
- Семафор означает механизм сигнализации, тогда как Мьютекс — это механизм блокировки.
- Семафор позволяет нескольким потокам получить доступ к критической секции.
- Одним из самых больших ограничений семафора является инверсия приоритета.
- Алгоритм циклического планирования с примером
- Синхронизация процессов: проблема критической секции в ОС
- Планирование процессов в ОС: долгосрочный, средний, краткосрочный планировщик
- Алгоритм приоритетного планирования: упреждающий, невытесняющий ПРИМЕР
- SSD против HDD: в чем разница между SSD и HDD