Семафоры, или как разруливать доступ к ресурсам в DBMS Caché

Часто при многопользовательском или параллельном доступе к данным возникает ситуация, когда необходимо заблокировать/дать доступ к переменной или участку памяти одновременно нескольким процессам. Решается данная задача с помощью мьютексов, семафоров, мониторов и т.д. В данном посте рассмотрим как же реализован один из методов предоставления совместного доступа к данным — семафор — в СУБД Intersystems Caché.
image image

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

Семафор — неотрицательная целая переменная, над которой возможны два вида операций:

  • P-операция над семафором представляет собой попытку декремента значения семафора на 1. Если перед выполнением P-операции значение семафора было больше 0, то P-операция выполняется без задержек. Если перед выполнением P-операции значение семафора было равным 0, то процесс, выполняющий P-операцию, переводится в состояние ожидания до тех пор, пока значение семафора не станет большим 0.
  • V-операция над семафором представляет инкремент значения семафора на 1. Если при этом имеются процессы, задержанные на выполнении P-операции на данном семафоре, один из этих процессов выходит из состояния ожидания и может выполнить свою P-операцию.

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

Взаимное исключение на семафоре

Для реализации взаимного исключения, например, предотвращения возможности одновременного изменения двумя или более процессами общих данных, создается двоичный семафор S. Начальное значение этого семафора = 1. Критические секции кода (секции, которые могут одновременно выполняться только одним процессом) обрамляются «скобками» P(S) (в начале секции) и V(S) (в конце секции). Процесс, входящий в критическую секцию, выполняет операцию P(S) и переводит семафор в 0. Если в критической секции уже находится другой процесс, то значение семафора уже 0, тогда второй процесс, желающий войти в критическую секцию, блокируется в своей P-операции до тех пор, пока процесс, находящийся сейчас в критической секции, не выйдет из неё, выполнив на выходе операцию V(S).

Синхронизация на семафоре

Для обеспечения синхронизации создаётся двоичный семафор S с начальным значением 0. Значение 0 означает, что событие ещё не наступило. Процесс, сигнализирующий о наступлении события, выполняет операцию V(S), устанавливающую семафор в 1. Процесс, ожидающий наступления события, выполняет операцию P(S). Если к этому моменту событие уже произошло, ожидающий процесс продолжает выполняться, если же событие ещё не произошло, процесс переводится в состояние ожидания до тех пор, пока сигнализирующий процесс не выполнит V(S).

В случае, если одного и того же события ожидают несколько процессов, процесс, успешно выполнивший операцию P(S), должен вслед за ней выполнить V(S), чтобы продублировать сигнал о событии для следующего ожидающего процесса.

Семафор — счётчик ресурсов

Если у нас имеется N единиц некоторого ресурса, то для контроля его распределения создаётся общий семафор S с начальным значением N. Выделение ресурса сопровождается операцией P(S), освобождение — операцией V(S). Значение семафора, таким образом, отражает число свободных единиц ресурса. Если значение семафора = 0, то есть свободных единиц больше не остаётся, то очередной процесс, запрашивающий единицу ресурса будет перенесён в ожидание в операции P(S) до тех пор, пока какой-либо из использующих ресурс процессов не освободит единицу ресурса, выполнив при этом V(S).
Все эти варианты можно реализовать в Caché, используя появившийся в версии 2014.2 класс %SYSTEM.Semaphore, который инкапсулирует 64-битное неотрицательное целое число и предоставляет методы изменения его значения всем запущенным процессам.

Рассмотрим использование семафора-счётчика на модельном примере (третий вариант использования).

Предположим, что на университет выделили 10 слотов для доступа к международной базе данных научных статей. Таким образом у нас есть ресурс, который нам надо разделить между всеми студентами, желающими получить доступ. У каждого студента есть свой логин/пароль для доступа к БД, но одновременно могут работать от университета только 10 человек. Каждый раз, когда студент логинится в БД, надо на 1 уменьшить количество доступных для использования слотов. Соответственно когда он выходит из БД, надо вернуть этот 1 слот в пул общего доступа.

Для того, чтобы проверить, давать ли студенту доступ или заставлять его ждать, используем семафор-счётчик, начальное значение которого равняется 10. Чтобы можно было посмотреть, как происходит процесс выдачи доступа, используем логирование. Главный класс будет инициализировать переменные и отслеживать процесс работы студентов. Ещё один класс будет наследоваться от %SYSTEM.Semaphore и реализовывать семафор. Отдельный класс выделим для различных утилит. И последние два класса будут имитировать вход и выход студента из БД. Поскольку сервер «знает» имя пользователя, который заходит в систему и выходит из неё, то для моделирования данного «знания» заведём отдельный глобал, который будет хранить информацию об активных пользователях (^LoggedUsers). В него будем записывать логины вошедших студентов и из него будем брать случайные имена для выхода из системы.

Начнём с класса Main в котором:

  1. создаём семафор,
  2. устанавливаем его начальное значение (равное 10, что соответствует 10 свободным слотам для доступа к БД научных статей),
  3. останавливаем процесс и удаляем семафор,
  4. отображаем лог.

SemaphoreSample.Main

Class SemaphoreSample.Main Extends %RegisteredObject [ ProcedureBlock ]
{

/// драйвер для примера
ClassMethod Run()
{
    // инициализируем глобалы для логирования
    Do ##class(SemaphoreSample.Util).InitLog()
    Do ##class(SemaphoreSample.Util).InitUsers()
    
    Set msg = "Старт процесса"
    Do ..Log(msg)

    // создаем и инициализируем семафор
    Set inventory = ##class(SemaphoreSample.Counter).%New()
    If ('($ISOBJECT(inventory))) {
        Set msg = "Метод класса SemaphoreSample.Counter %New() не отработал"
        Do ..Log(msg)
        Quit
    }
    
    // устанавливаем начальное значение семафора
    if 'inventory.Init(10) {
	    Set msg = "Возникла проблема при инициализации семафора"
    	Do ..Log(msg)
	    Quit
	    }
    
    // ожидаем окончания процесса
    Set msg = "Нажмите любую клавишу для прекращения доступа..."
    Do ..Log(msg)
    
    Read *x
        
    //удаляем семафор
    Set msg = "Семафор удален со статусом " _ inventory.Delete()
    Do ..Log(msg)
    Set msg = " Окончание процесса"
    Do ..Log(msg)
        
    do ##class(SemaphoreSample.Util).ShowLog()
    
    Quit
}

/// Вызов утилиты для записи лога
ClassMethod Log(msg As %String) [ Private ]
{
    Do ##class(SemaphoreSample.Util).Logger($Horolog, "Main", msg)
    Quit
}

}

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

  1. подготовку глобала протоколирования к работе (^SemaphoreLog),
  2. запись логов в глобал,
  3. отображение логов,
  4. занесение в глобал имён работающих пользователей (^LoggedUsers),
  5. выбор случайного имени из работающих пользователей,
  6. удаление имени из глобала работающих пользователей по индексу.

SemaphoreSample.Util

Class SemaphoreSample.Util Extends %RegisteredObject [ ProcedureBlock ]
{

/// инициализация лога
ClassMethod InitLog()
{
    // удаляем предыдущие записи из лога
    Kill ^SemaphoreLog
    Set ^SemaphoreLog = 0
   
    Quit
}

/// инициализация лога
ClassMethod InitUsers()
{
	//на всякий случай удаляем всех пользователей из глобала
        if $data(^LoggedUsers) '= 0
	{
		Kill ^LoggedUsers    	
	}
	Set ^LoggedUsers = 0
}

/// непосредственно запись лога в глобал
ClassMethod Logger(time As %DateTime, sender As %String, msg As %String)
{
    Set inx = $INCREMENT(^SemaphoreLog)
    Set ^SemaphoreLog(inx, 0) = time
    Set ^SemaphoreLog(inx, 1) = sender
    Set ^SemaphoreLog(inx, 2) = msg
    Write "(", ^SemaphoreLog, ") ", msg_" в "_$ztime($PIECE(time,",",2), 1), !
    Quit
}

/// вывод сообщений на экран
ClassMethod ShowLog()
{
    Set msgcnt = $GET(^SemaphoreLog, 0)
    Write "Лог сообщений: количество записей = ", msgcnt, !, !
    Write "#", ?5, "Время", ?12, "Отправитель", ?25, "Сообщение", !
    
    For i = 1 : 1 : msgcnt {
        Set time = ^SemaphoreLog(i, 0)
        Set sender = ^SemaphoreLog(i, 1)
        Set msg = ^SemaphoreLog(i, 2)
        Write i, ")", ?5, $ztime($PIECE(time,",",2), 1), ?15, sender, ":", ?35, msg, !
    }
    Quit
}

/// добавление имени пользователя в список залогиненых
ClassMethod AddUser(Name As %String)
{   
	Set inx = $INCREMENT(^LoggedUsers)
	set ^LoggedUsers(inx) = Name
}

/// удаление имени пользователя из списка залогиненных
ClassMethod DeleteUser(inx As %Integer)
{	
	kill ^LoggedUsers(inx)
}

/// выбор имени пользователя из списка залогиненных пользователей
ClassMethod ChooseUser(ByRef Name As %String) As %Integer
{	
	// если все пользователи "вышли", то возвращаем признак необходимости подождать
        if $data(^LoggedUsers) = 1
	{
	   Set Name = ""
	   Quit -1
	} else
	{
		Set Temp = ""
		Set Numb = $Random(10)+5
   		For i = 1 : 1: Numb {
      		   Set Temp = $Order(^LoggedUsers(Temp))     
                   // для того, чтобы зациклить проход по одному уровню глобала
                   // по окончанию одного прохода перемещаем указатель в начало 
      		   if (Temp = "")      
      		   {
        		set Temp = $Order(^LoggedUsers(""))
      		   }
   		}   
   		set Name = ^LoggedUsers(Temp)
   		Quit Temp
       }
}

}

Далее класс с реализацией самого семафора. Его наследуем от системного класса %SYSTEM.Semaphore и добавляем методы, реализующие вызов

  1. метода, возвращающего уникальное имя семафора,
  2. метода записи событий в лог,
  3. callback методов создания и уничтожения семафора (в них просто отмечаем факт создания/уничтожения),
  4. метода создания и инициализации семафора.

SemaphoreSample.Counter

Class SemaphoreSample.Counter Extends %SYSTEM.Semaphore
{

/// Каждый счетчик должен имет свое уникальное имя
ClassMethod Name() As %String
{
    Quit "Counter"
}

/// Вызов утилиты для записи лога
Method Log(Msg As %String) [ Private ]
{
    Do ##class(SemaphoreSample.Util).Logger($Horolog, ..Name(), Msg)
    Quit
}

/// Callback метод при создании нового объекта
Method %OnNew() As %Status
{
    Set msg = "Создание нового семафора"
    Do ..Log(msg)
    Quit $$$OK
}

/// Создание и инициализация семафора
Method Init(initvalue = 0) As %Status
{
    Try {
        If (..Create(..Name(), initvalue)) {
            Set msg = "Создан: """ _ ..Name() 
                    _ """; Начальное значение = " _ initvalue
            Do ..Log(msg)
            Return 1
        }
        Else {
        Set msg = "Возникла проблема при создании семафора с именем = """ _ ..Name() _ """"
        Do ..Log(msg)
        Return 0
        }
    } Catch errobj {
        Set msg = "Возникла ошибка при создании семафора: "_errobj.Data
        Do ..Log(msg)
        Return 0
    }
}

/// Callback метод при закрытии объекта
Method %OnClose() As %Status [ Private ]
{
    Set msg = "Закрываем семафор"
    Do ..Log(msg)
    Quit $$$OK
}

}

Примечание об именовании семафоров

Семафоры определяются по имени, которое передаётся системе в момент создания семафора. И это имя должно соответствовать требованиям к локальным/глобальным переменным. Естественно, имя семафора должно быть уникальным. Обычно семафор хранится в инстансе БД, в котором он был создан, и он видим для всех других процессов этого инстанса. Если имя семафора соответствует правилам задания имён глобальных переменных, то семафор становится доступным всем запущенным процессам, включая ECP.

И последние два класса имитируют входящих в систему и выходящих из неё пользователей. Чтобы упростить пример, предположим, что их ровно 25 человек. Конечно, можно было бы запустить процесс и создавать нового пользователя пока не будет нажата какая-нибудь клавиша на клавиатуре, но я решила сделать проще и использовать конечный цикл. В обоих классах сначала подключаемся к существующему семафору и пробуем уменьшать (входить в систему) / увеличивать (выходить из системы) счётчик. Предполагаем, что студент будет своей очереди ждать бесконечно долго (ну очень ему надо получить доступ), поэтому используем функцию Decrement, которая позволяет нам задать бесконечный период ожидания. В противном случае можем поставить конкретный промежуток времени до таймаута в десятых долях секунды. Чтобы все пользователи одновременно не ломились в систему, ставим некоторую произвольную паузу перед следующим логином.

SemaphoreSample.LogIn

Class SemaphoreSample.LogIn Extends %RegisteredObject [ ProcedureBlock ]
{

/// Моделирование входа пользователей в систему
ClassMethod Run() As %Status
{
    
    //открываем семафор, отвечающий за доступ к БД
    Set cell = ##class(SemaphoreSample.Counter).%New()
    Do cell.Open(##class(SemaphoreSample.Counter).Name())
    
    // начинаем "логиниться" в систему
    // для примера берем 25 разных студентов
    For deccnt = 1 : 1 : 25 {        
        // генерируем случайный логин
   	Set Name = ##class(%Library.PopulateUtils).LastName()
   		
        try
        {
	       Set result =  cell.Decrement(1, -1)  
        } catch 
        {
	       Set msg = "Доступ прекращен"
	       Do ..Logger(##class(SemaphoreSample.Counter).Name(), msg)
	       Return   
	    }
        do ##class(SemaphoreSample.Util).AddUser(Name)      
        Set msg = Name _ " зашел в систему"
        Do ..Logger(Name, msg)
        
        Set waitsec = $RANDOM(10) + 7
        Hang waitsec
    }
    Set msg = "Желающие зайти в систему закончились"
    Do ..Logger(##class(SemaphoreSample.Counter).Name(), msg)
    Quit $$$OK
}

/// Вызов утилиты для записи лога
ClassMethod Logger(id As %String, msg As %String) [ Private ]
{
    Do ##class(SemaphoreSample.Util).Logger($Horolog, id, msg)
    Quit
}

}

При отключении от сервера в нашей модели надо проверять, а есть ли там вообще пользователи. Поэтому сначала смотрим на содержимое глобала с пользователями (^LoggedUsers) и если он пустой, то ждем некоторое произвольное время и ещё раз проверяем успел ли кто-нибудь зайти в систему.

SemaphoreSample.LogOut

Class SemaphoreSample.LogOut Extends %RegisteredObject [ ProcedureBlock ]
{

/// Моделирование выхода пользователей из системы
ClassMethod Run() As %Status
{
    Set cell = ##class(SemaphoreSample.Counter).%New()
    Do cell.Open(##class(SemaphoreSample.Counter).Name())
    
    // выходим из системы
    For addcnt = 1 : 1 : 25 {
        Set inx = ##class(SemaphoreSample.Util).ChooseUser(.Name)
        while inx = -1
        {
	        Set waitsec = $RANDOM(10) + 1
        	Hang waitsec
        	Set inx = ##class(SemaphoreSample.Util).ChooseUser(.Name)
        }
        try 
        {
        	Do cell.Increment(1)
        } catch 
        {
	        Set msg = "Доступ прекращен"
	        Do ..Logger(##class(SemaphoreSample.Counter).Name(), msg)
	        Return   
	    }
        
        
        Set waitsec = $RANDOM(15) + 2
        Hang waitsec
    }
    Set msg = "Все пользователи вышли из системы"
    Do ..Logger(##class(SemaphoreSample.Counter).Name(), msg)
    Quit $$$OK
}

/// Вызов утилиты для записи лога
ClassMethod Logger(id As %String, msg As %String) [ Private ]
{
    Do ##class(SemaphoreSample.Util).Logger($Horolog, id, msg)
    Quit
}

}

Теперь проект готов. Его компилируем и можно запускать и смотреть что получилось. Запускать будем в трёх разных окнах Терминала.

В первом окне при необходимости переходим в нужную область имен (я делала проект в области имен USER)

zn "USER"

и вызываем метод запуска нашего «сервера» из класса Main:

do ##class(SemaphoreSample.Main).Run()

Во втором окне вызываем метод Run из класса LogIn, который будет генерить пользователей, которые входят в систему:

do ##class(SemaphoreSample.LogIn).Run()

И в последнем окне вызываем метод Run из класса LogOut, который будет генерить пользователей, которые выходят из системы:

do ##class(SemaphoreSample.LogOut).Run()

После того, как все зашли и вышли имеем такие результаты в окнах:

В первом окне будет лог

(1) Старт процесса в 21:50:44
(2) Создание нового семафора в 21:50:44
(3) Создан: "Counter"; Начальное значение = 10 в 21:50:44
(4) Нажмите любую клавишу для прекращения доступа... в 21:50:44
(61) Семафор удален со статусом 1 в 22:00:16
(62)  Окончание процесса в 22:00:16
Лог сообщений: количество записей = 62
 
#    Время  Отправитель  Сообщение
1)   21:50:44  Main:               Старт процесса
2)   21:50:44  Counter:            Создание нового семафора
3)   21:50:44  Counter:            Создан: "Counter"; Начальное значение = 10
4)   21:50:44  Main:               Нажмите любую клавишу для прекращения доступа...
5)   21:51:00  Counter:            Создание нового семафора
6)   21:51:00  Zemaitis:           Zemaitis зашел в систему
7)   21:51:12  Goldman:            Goldman зашел в систему
8)   21:51:24  Cooke:              Cooke зашел в систему
9)   21:51:39  Kratzmann:          Kratzmann зашел в систему
10)  21:51:47  Roentgen:           Roentgen зашел в систему
11)  21:51:59  Xerxes:             Xerxes зашел в систему
12)  21:52:10  Houseman:           Houseman зашел в систему
13)  21:52:18  Wijnschenk:         Wijnschenk зашел в систему
14)  21:52:33  Orwell:             Orwell зашел в систему
15)  21:52:49  Gomez:              Gomez зашел в систему
16)  21:53:46  Counter:            Создание нового семафора
17)  21:53:46  Kratzmann:          Kratzmann вышел из системы
18)  21:53:46  Quilty:             Quilty зашел в систему
19)  21:54:00  Orwell:             Orwell вышел из системы
20)  21:54:00  Kelvin:             Kelvin зашел в систему
21)  21:54:11  Goldman:            Goldman вышел из системы
22)  21:54:11  Nelson:             Nelson зашел в систему
23)  21:54:23  Gomez:              Gomez вышел из системы
24)  21:54:23  Ragon:              Ragon зашел в систему
25)  21:54:30  Zemaitis:           Zemaitis вышел из системы
26)  21:54:31  Quilty:             Quilty зашел в систему
27)  21:54:42  Nelson:             Nelson вышел из системы
28)  21:54:42  Williams:           Williams зашел в систему
29)  21:54:49  Houseman:           Houseman вышел из системы
30)  21:54:52  Quilty:             Quilty вышел из системы
31)  21:54:58  Macrakis:           Macrakis зашел в систему
32)  21:55:00  Xerxes:             Xerxes вышел из системы
33)  21:55:02  Quilty:             Quilty вышел из системы
34)  21:55:04  Cooke:              Cooke вышел из системы
35)  21:55:08  Brown:              Brown зашел в систему
36)  21:55:14  Williams:           Williams вышел из системы
37)  21:55:16  Yancik:             Yancik зашел в систему
38)  21:55:17  Kelvin:             Kelvin вышел из системы
39)  21:55:26  Roentgen:           Roentgen вышел из системы
40)  21:55:27  Jaynes:             Jaynes зашел в систему
41)  21:55:34  Jaynes:             Jaynes вышел из системы
42)  21:55:34  Rogers:             Rogers зашел в систему
43)  21:55:47  Basile:             Basile зашел в систему
44)  21:55:50  Rogers:             Rogers вышел из системы
45)  21:55:58  Yancik:             Yancik вышел из системы
46)  21:56:02  Taylor:             Taylor зашел в систему
47)  21:56:09  Ahmed:              Ahmed зашел в систему
48)  21:56:11  Taylor:             Taylor вышел из системы
49)  21:56:15  Wijnschenk:         Wijnschenk вышел из системы
50)  21:56:23  Edwards:            Edwards зашел в систему
51)  21:56:29  Edwards:            Edwards вышел из системы
52)  21:56:32  Counter:            Желающие зайти в систему закончились
53)  21:56:32  Counter:            Закрываем семафор
54)  21:56:42  Basile:             Basile вышел из системы
55)  21:56:58  Macrakis:           Macrakis вышел из системы
56)  21:57:11  Ahmed:              Ahmed вышел из системы
57)  21:57:18  Ragon:              Ragon вышел из системы
58)  21:57:31  Brown:              Brown вышел из системы
59)  21:57:36  Counter:            Все пользователи вышли из системы
60)  21:57:36  Counter:            Закрываем семафор
61)  22:00:16  Main:               Семафор удален со статусом 1
62)  22:00:16  Main:                Окончание процесса
(63) Закрываем семафор в 22:00:16

Во втором окне будет лог

(5) Создание нового семафора в 21:51:00
(6) Zemaitis зашел в систему в 21:51:00
(7) Goldman зашел в систему в 21:51:12
(8) Cooke зашел в систему в 21:51:24
(9) Kratzmann зашел в систему в 21:51:39
(10) Roentgen зашел в систему в 21:51:47
(11) Xerxes зашел в систему в 21:51:59
(12) Houseman зашел в систему в 21:52:10
(13) Wijnschenk зашел в систему в 21:52:18
(14) Orwell зашел в систему в 21:52:33
(15) Gomez зашел в систему в 21:52:49
(18) Quilty зашел в систему в 21:53:46
(20) Kelvin зашел в систему в 21:54:00
(22) Nelson зашел в систему в 21:54:11
(24) Ragon зашел в систему в 21:54:23
(26) Quilty зашел в систему в 21:54:31
(28) Williams зашел в систему в 21:54:42
(31) Macrakis зашел в систему в 21:54:58
(35) Brown зашел в систему в 21:55:08
(37) Yancik зашел в систему в 21:55:16
(40) Jaynes зашел в систему в 21:55:27
(42) Rogers зашел в систему в 21:55:34
(43) Basile зашел в систему в 21:55:47
(46) Taylor зашел в систему в 21:56:02
(47) Ahmed зашел в систему в 21:56:09
(50) Edwards зашел в систему в 21:56:23
(52) Желающие зайти в систему закончились в 21:56:32
(53) Закрываем семафор в 21:56:32

В третьем окне будет лог

(16) Создание нового семафора в 21:53:46
(17) Kratzmann вышел из системы в 21:53:46
(19) Orwell вышел из системы в 21:54:00
(21) Goldman вышел из системы в 21:54:11
(23) Gomez вышел из системы в 21:54:23
(25) Zemaitis вышел из системы в 21:54:30
(27) Nelson вышел из системы в 21:54:42
(29) Houseman вышел из системы в 21:54:49
(30) Quilty вышел из системы в 21:54:52
(32) Xerxes вышел из системы в 21:55:00
(33) Quilty вышел из системы в 21:55:02
(34) Cooke вышел из системы в 21:55:04
(36) Williams вышел из системы в 21:55:14
(38) Kelvin вышел из системы в 21:55:17
(39) Roentgen вышел из системы в 21:55:26
(41) Jaynes вышел из системы в 21:55:34
(44) Rogers вышел из системы в 21:55:50
(45) Yancik вышел из системы в 21:55:58
(48) Taylor вышел из системы в 21:56:11
(49) Wijnschenk вышел из системы в 21:56:15
(51) Edwards вышел из системы в 21:56:29
(54) Basile вышел из системы в 21:56:42
(55) Macrakis вышел из системы в 21:56:58
(56) Ahmed вышел из системы в 21:57:11
(57) Ragon вышел из системы в 21:57:18
(58) Brown вышел из системы в 21:57:31
(59) Все пользователи вышли из системы в 21:57:36
(60) Закрываем семафор в 21:57:36

Теперь более внимательно посмотрим что же происходило. Я специально дождалась, когда в систему «зайдёт» 10 позволенных пользователей, чтобы показать, что значение семафора не может быть меньше 0. Это пользователи над красной линией на рисунке. Видим, что есть перерыв почти в минуту перед тем, как позволяется зайти в систему следующему пользователю. И то только после того, как кто-то вышел. Я специально выделила жирным пользователей, которые входят в систему, и зачёркнутым курсивом тех, кто выходит из неё. Цветами выделены пары входа/выхода для каждого пользователя.

image

Как по мне, вышла интересная картинка.

Кроме использованных в примере методов Increment и Decrement для работы с семафором можно использовать список ожидания и методы работы с ним:

  • AddToWaitMany — добавление операции над семафором в список
  • RemoveFromWaitMany — удаление операции из списка
  • WaitMany — ожидание, пока все операции над семафором не завершатся

В этом случае WaitMany в цикле отрабатывает все находящиеся в списке задачи и, после успешного выполнения операции, вызывает Callback метод WaitCompleted, который разработчик должен реализовать самостоятельно. Он вызывается когда семафор выделил ненулевое значение для операции или ожидание вышло по таймауту. Число, на которое счётчик был уменьшен, возвращается в аргумент данного метода (0 в случае таймаута). После того, как этот метод отработал, семафор удаляется из списка задач WaitMany и дальше осуществляется переход на следующую задачу.

Больше информации по семафорам в Caché можно прочесть в документации (там же можно посмотреть ещё один пример работы с семафором) и в описании класса. Поскольку на момент создания статьи версия Caché 2014.2 доступна только на филд-тест портале для пользователей на поддержке и участников академической программы InterSystems University, то ссылки будут добавлены, как только выйдет релиз.

Проект находится на GitHub.

Если есть комментарии, замечания или предложения — милости прошу. Благодарю за внимание!

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *