Статьи

Программирование монитора в JR

Как я и обещал, я перезапущу серию про JR, когда период оценки закончился. Посмотрев, как разрабатывать с использованием языка программирования JR , теперь мы увидим, как использовать мониторы в JR.

Мониторы обеспечивают более высокий уровень абстракции, чем семафоры, и создают лучший код с несколькими преимуществами:

  • Весь код синхронизации централизован в одном месте, и пользователям этого кода не нужно знать, как он реализован.
  • Код не зависит от количества процессов, он работает столько раз, сколько вы хотите
  • Вам не нужно выпускать что-то вроде мьютекса, поэтому вы не можете забыть сделать это

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

Теперь посмотрим, как их использовать в JR.

В JR у вас нет возможности создавать мониторы напрямую, но есть препроцессор, который преобразует файл с обоими операциями JR и мониторов в простой файл JR. Этот процессор называется m2jr (монитор для JR) и доступен непосредственно в дистрибутиве JR. Таким образом, вам нужно всего лишь использовать команду m2jr для преобразования файла .m (обычный файл расширения монитора) в несколько файлов JR (один для монитора и один для переменных условия). Это обычный файл JR с несколькими комментариями для облегчения отладки (соответствует между строками в m и JR).

Все ключевые слова языка m2jr начинаются с _ (подчеркивание). Чтобы объявить монитор, достаточно просто использовать ключевое слово _monitor:

_monitor MonitorTest {
//...
}

Чтобы добавить методы к монитору, вам просто нужно создать метод с префиксом _proc:

_monitor MonitorTest {
_proc void testA(){
//Some code
}
}

Только при этом гарантируется взаимное исключение. В монитор допускается только один процесс. Если вам нужны методы с возвращаемым типом, вы должны использовать _return вместо return:

_monitor MonitorTest {
_proc void testA(){
//Some code
}

_proc int testB(){
_return 1;
}
}

Для объявления условных переменных используется ключевое слово _condvar. Вам не нужно их инициализировать, просто объявите их:

_monitor MonitorTest {
_condvar condVar1;
_condvar condVar2;
}

Чтобы использовать глобальные переменные, вы должны поставить перед ними префикс _var:

_monitor MonitorTest {
_var var1;
_var var2;
}

А для выполнения операций над условными переменными вы должны использовать методы _signal и _wait внутри метода proc:

_monitor MonitorTest {
_condvar a;
_condvar b;

_proc void test(){
_wait(a);
//Some computations
_signal(b);
}
}

И вы компилируете это в JR, используя простую команду:

m2jr MonitorTest.m

Это создаст два файла (MonitorTest.jr и c_m_condvar.jr). Конечно, вы можете использовать этот монитор в обычном классе JR, вам не нужно компилировать классы, которые используют мониторы с m2jr, только с компилятором JR. Есть только одна вещь для беспокойства. m2jr генерирует конструктор, который принимает строку в каждом классе мониторов. Когда вы создаете экземпляр монитора, вы должны предоставить строку, представляющую его имя в качестве первого параметра. И если вы хотите создать новый конструктор в мониторе, вам просто нужно вызвать super с помощью String. Мы увидим пример позже.

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

  • Сигнал и продолжение (SC): процесс, который сигнализирует о взаимном исключении, и сигнал будет пробужден, но перед тем, как идти, необходимо получить взаимное исключение.
  • Сигнал и ожидание (SW): Сигнализатор заблокирован и должен дождаться продолжения взаимного исключения, а сигнальный поток непосредственно пробуждается и может начать продолжить свои операции.
  • Сигнал и срочное ожидание (SU): Как и SW, но поток сигнализатора имеет гарантию, чем если бы он шел сразу после сигнального потока
  • Сигнал и выход (SX): Сигнализатор выходит из метода сразу после сигнала, и сигнальный поток может начинаться напрямую. Эта философия не часто используется.

По умолчанию m2jr выполняет компиляцию с помощью SC, но вы можете настроить ее для использования других принципов. Просто добавьте аббревиатуру философии (строчные буквы) в качестве опции для компилятора m2jr. Например, для компиляции с использованием Signal & Exit:

m2jr -sx MonitorTest.m

Основное отличие состоит в том, что СЦ создают проблему кражи сигнала . Вы быстро разберетесь с примером. Теперь мы можем создать монитор для управления ограниченным буфером:

_monitor BoundedBuffer {
private static final int N = 5; //Size of the buffer

_var String[] buffer = new String[N];
_var int front;
_var int rear;
_var int count;

_condvar notFull;
_condvar notEmpty;

_proc void deposit(String data){
while(count == N){
_wait(notFull);
}

buffer[rear] = data;
rear = (rear + 1) % N;
count++;

_signal(notEmpty);
}

_proc String fetch(){
while(count == 0){
_wait(notEmpty);
}

String result = buffer[front];
front = (front + 1) % N;
count--;

_signal(notFull);

_return result;
}
}

Некоторым из вас наверняка покажется странным то, что вокруг операции ожидания есть цикл. Это чтобы избежать проблемы кражи сигнала. Если не было цикла while, представьте себе такую ​​ситуацию в SC:

  1. Поток 1 пытается сделать fetch (), данных нет, поэтому он ожидает условную переменную notEmpty
  2. Поток 2 делает deposit (), который пробуждает поток 1, но ему необходимо снова получить взаимное исключение.
  3. До того как поток 2 получил взаимное исключение, поток 3 выполняет выборку (), данных достаточно, поэтому поток 3 выполняет выборку и получает данные. Итак, сейчас пусто.
  4. Поток 2 получает право зайти в монитор и получить данные. Но подождите минуту, данных нет, и он извлечет нулевые данные или, возможно, старые данные, все еще выбранные, в зависимости от текущего состояния буфера

Чтобы избежать такой ситуации, вам нужно просто обернуть ожидание в цикл while вместо if и все готово! Или вы также можете использовать SW вместо SC.

С этим монитором мы можем легко решить проблему производителя и потребителя:

public class ProducerConsumer {
private static final int N = 12; //Number of producers and consumers

private static BoundedBuffer bb = new BoundedBuffer("Bounded Buffer monitor"); //The monitor

public static void main(String... args){}

private static process Producer((int i = 0; i < N; i++)){
bb.deposit("Producer" + i);
}

private static process Consumer((int i = 0; i < N; i++)){
System.out.println("Consumer" + i + " : " + bb.fetch());
}
}

Это обеспечит вывод:

Потребитель10: Производитель0
Потребитель0: Производитель1
Потребитель1: Производитель2
Потребитель2: Производитель4
Потребитель3: Производитель5
Потребитель4: Производитель3
Потребитель8: Производитель7
Потребитель11: Производитель10
Потребитель6: Производитель8
Потребитель5: Производитель6
Потребитель7: Производитель11
Потребитель9: Производитель9

Так что это работает хорошо.

Больше, чем операции «сигнал» и «ожидание», m2jr обеспечивает и другие операции над переменными условия:

  • _signal_all: разбудить весь процесс очереди ожидания. Эта операция предоставляется только в режиме «Сигнал и продолжение».
  • _empty: проверить, есть ли у переменной условия какой-либо ожидающий процесс
  • _wait (condvar, int priority): ставить процесс в очередь с заданным приоритетом. Если вы используете это, очередь теперь управляется как приоритетная очередь. Вы не можете использовать и ожидание без приоритета, и ожидание с приоритетом для одной и той же условной переменной
  • _minrank: вернуть минимальный приоритет для заданной условной переменной. Если переменная условия не имеет официантов, возвращаемое заданное число не выглядит ничем.

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

Надеюсь, вы нашли этот пост интересным. Следующий пост о JR будет посвящен операциям и возможностям.

С http://www.baptiste-wicht.com/2010/06/monitor-programming-in-jr/