Often, with multi-user or parallel data access, a situation arises when it is necessary to block / give access to a variable or memory section to several processes simultaneously. This task is solved with the help of mutexes, semaphores, monitors, etc. In this post, we will look at how one of the methods for providing shared data access, the
semaphore, is implemented in Intersystems Caché.


Semaphores are flexible and convenient means for synchronization and mutual exclusion of processes, accounting of resources. Hidden semaphores are also used in operating systems as the basis for other means of interaction between processes.
A semaphore is a non-negative integer variable, over which two types of operations are possible:
- A P-operation on a semaphore is an attempt to reduce the value of a semaphore by 1. If the value of the semaphore was greater than 0 before performing the P-operation, then the P-operation is performed without delay. If, before performing a P operation, the value of the semaphore was 0, then the process performing the P operation is placed in the wait state until the value of the semaphore becomes greater than 0.
- A V-operation on a semaphore represents an increment of a semaphore value by 1. If there are processes that are delayed during the execution of a P-operation on a given semaphore, one of these processes goes out of the idle state and can perform its P-operation.
Semaphores are of two types: binary (picture on the left) and the general form, or counters (picture on the right). They differ in that in the first case the variable can take only two values: 0 and 1, and in the second - any non-negative integer. There are several options for using semaphores.
Mutual exclusion on semaphoreTo implement mutual exclusion, for example, to prevent the possibility of simultaneous modification by two or more common data processes, a binary semaphore S is created. The initial value of this semaphore = 1. Critical sections of code (sections that can only be executed by one process at a time) are framed by brackets P ( S) (at the beginning of the section) and V (S) (at the end of the section). The process included in the critical section performs operation P (S) and translates the semaphore to 0. If another process is already in the critical section, then the semaphore value is already 0, then the second process that wants to enter the critical section is blocked in its P-operation until the process that is now in the critical section comes out of it by executing operation V (S) at the output.
Semaphore syncTo ensure synchronization, a binary semaphore S is created with an initial value of 0. A value of 0 means that the event has not yet arrived. The process signaling the occurrence of an event performs an operation V (S) setting the semaphore to 1. The process awaiting the occurrence of an event performs an operation P (S). If at this point the event has already occurred, the waiting process continues to run, if the event has not yet occurred, the process is transferred to the wait state until the signaling process executes V (S).
')
If several processes are waiting for the same event, the process that successfully completed operation P (S) must follow V (S) after it in order to duplicate the event signal for the next pending process.
Semaphore - Resource CounterIf we have N units of a certain resource, then to control its distribution, a common semaphore S is created with an initial value N. Resource allocation is accompanied by operation P (S), release - by operation V (S). The semaphore value thus reflects the number of free resource units. If the semaphore value = 0, that is, there is no more free units, then the next process, the requesting resource unit, will be transferred to the pending operation P (S) until any of the processes using the resource releases the resource unit by executing this v (s).
All these options can be implemented in Caché using the class
% SYSTEM.Semaphore , which appeared in version
2014.2 , which encapsulates a 64-bit non-negative integer and provides methods for changing its value to all running processes.
Consider the use of a semaphore counter on a model example (the third use case).
Suppose that a university has allocated 10 slots for access to an international database of scientific articles. Thus, we have a resource that we need to share among all students who wish to gain access. Each student has his own login / password for access to the database, but only 10 people from the university can work at the same time. Every time a student logs into the database, it is necessary to reduce the number of available slots by 1. Accordingly, when it leaves the database, this 1 slot must be returned to the shared pool.In order to check whether to give a student access or make him wait, we use a semaphore counter, the initial value of which equals 10. To see how the process of issuing access occurs, we use logging. The main class will initialize the variables and monitor the work process of the students. Another class will inherit from% SYSTEM.Semaphore and implement the semaphore. Separate class will allocate for various utilities. And the last two classes will simulate the entrance and exit of the student from the database. Since the server “knows” the name of the user who logs in and out of the system, we will
create a separate global for modeling this “knowledge”, which will store information about active users (
^ LoggedUsers ). In it we will write logins of the entered students and from it we will take random names for exit from the system.
Let's start with the Main class in which:
- create a semaphore
- we set its initial value (equal to 10, which corresponds to 10 free slots for access to the database of scientific articles),
- stop the process and remove the semaphore,
- display the log.
SemaphoreSample.MainClass 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 (
The next class to create is a class with various utilities. They will be needed for the test application. There will be class methods responsible for:
- global logging preparation for work ( ^ SemaphoreLog ),
- write logs to global
- display logs
- entering into the global names of working users ( ^ LoggedUsers ),
- random name selection from working users,
- Removing a name from the global working users by index.
SemaphoreSample.Util Class SemaphoreSample.Util Extends %RegisteredObject [ ProcedureBlock ] { /// ClassMethod InitLog() { // Kill ^SemaphoreLog Set ^SemaphoreLog = 0 Quit } /// ClassMethod InitUsers() { // if $data(^LoggedUsers)
Next, the class with the implementation of the semaphore. We inherit it from the% SYSTEM.Semaphore system class and add methods that implement the call
- a method that returns a unique semaphore name,
- method of recording events in the log,
- callback methods for creating and destroying a semaphore (they just note the fact of creation / destruction),
- method of creating and initializing the semaphore.
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 } }
Note on semaphore namingSemaphores are defined by the name that is passed to the system at the time the semaphore was created. And this name must meet the requirements for local / global variables. Naturally, the name of the semaphore must be unique. Typically, the semaphore is stored in the instance of the database in which it was created, and it is visible to all other processes of this instance. If the semaphore name complies with the global variable naming rules, then the semaphore becomes available to all running processes, including ECP.
And the last two classes imitate the users entering into the system and leaving it. To simplify the example, suppose that there are exactly 25 people. Of course, it would be possible to start the process and create a new user until some key is pressed on the keyboard, but I decided to make it easier and use the end loop. In both classes, we first connect to the existing semaphore and try to reduce (log in) / increase (log out) the counter. We assume that the student will wait for his turn indefinitely (well, he really needs to get access), so we use the function
Decrement , which allows us to set an infinite waiting period. Otherwise, we can set a specific time interval to the timeout in tenths of a second. So that all users do not simultaneously break into the system, put some arbitrary pause before the next login.
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 } }
When disconnecting from the server in our model, you need to check if there are any users there at all. Therefore, we first look at the contents of the global with users (
^ LoggedUsers ) and if it is empty, then we wait for some arbitrary time and check once again whether someone has logged in to the system.
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 } }
Now the project is ready. We compile it and you can run and watch what happened. We will run in three different windows of the Terminal.
In the first window, if necessary, go to the desired namespace (I did a project in the USER namespace)
zn "USER"
and call the start method of our “server” from the
Main class:
do ##class(SemaphoreSample.Main).Run()
In the second window, we call the
Run method from the
LogIn class, which will generate users who log into the system:
do ##class(SemaphoreSample.LogIn).Run()
And in the last window, we call the
Run method from the
LogOut class, which will generate users who log out:
do ##class(SemaphoreSample.LogOut).Run()
After everyone has logged in and out, we have the following results in the windows:
The first window will log(1) The process starts at 21:50:44
(2) Creating a new semaphore at 21:50:44
(3) Created: “Counter”; Initial value = 10 at 21:50:44
(4) Press any key to stop access ... at 21:50:44
(61) The semaphore is deleted with status 1 at 10:00:16
(62) End of the process at 22:00:16
Log messages: number of records = 62
# Time Sender Message
1) 21:50:44 Main: Start of the process
2) 21:50:44 Counter: Creating a new semaphore
3) 9:50:44 Counter: Created: “Counter”; Initial value = 10
4) 9:50:44 PM Main: Press any key to stop access ...
5) 21:51:00 Counter: Creating a new semaphore
6) 21:51:00 Zemaitis: Zemaitis is logged in
7) 21:51:12 Goldman: Goldman logged in
8) 21:51:24 Cooke: Cooke logged in
9) 21:51:39 Kratzmann: Kratzmann has logged in
10) 21:51:47 Roentgen: Roentgen logged in
11) 21:51:59 Xerxes: Xerxes logged in
12) 21:52:10 Houseman: Houseman logged in
13) 21:52:18 Wijnschenk: Wijnschenk is logged in
14) 21:52:33 Orwell: Orwell logged in
15) 21:52:49 Gomez: Gomez logged in
16) 21:53:46 Counter: Creating a new semaphore
17) 21:53:46 Kratzmann: Kratzmann logged out
18) 21:53:46 Quilty: Quilty logged in
19) 21:54:00 Orwell: Orwell is logged out
20) 21:54:00 Kelvin: Kelvin has logged in
21) 21:54:11 Goldman: Goldman logged out
22) 21:54:11 Nelson: Nelson logged in
23) 21:54:23 Gomez: Gomez is logged out
24) 21:54:23 Ragon: Ragon is logged in.
25) 21:54:30 Zemaitis: Zemaitis is logged out
26) 21:54:31 Quilty: Quilty logged in
27) 21:54:42 Nelson: Nelson is logged out
28) 21:54:42 Williams: Williams is logged in
29) 21:54:49 Houseman: Houseman is logged out
30) 21:54:52 Quilty: Quilty is logged out
31) 21:54:58 Macrakis: Macrakis is logged in
32) 21:55:00 Xerxes: Xerxes is logged out
33) 21:55:02 Quilty: Quilty is logged out
34) 21:55:04 Cooke: Cooke logged out
35) 21:55:08 Brown: Brown logged in
36) 21:55:14 Williams: Williams is logged out
37) 9:55:16 PM Yancik: Yancik has logged in
38) 21:55:17 Kelvin: Kelvin logged out
39) 21:55:26 Roentgen: Roentgen logged out
40) 21:55:27 Jaynes: Jaynes logged in
41) 21:55:34 Jaynes: Jaynes is logged out
42) 21:55:34 Rogers: Rogers Signed In
43) 21:55:47 Basile: Basile logged in
44) 21:55:50 Rogers: Rogers is logged out
45) 21:55:58 Yancik: Yancik logged out
46) 21:56:02 Taylor: Taylor has logged in
47) 21:56:09 Ahmed: Ahmed logged in
48) 21:56:11 Taylor: Taylor is logged out
49) 21:56:15 Wijnschenk: Wijnschenk is logged out
50) 21:56:23 Edwards: Edwards has logged in
51) 21:56:29 Edwards: Edwards is logged out
52) 21:56:32 Counter: Those wishing to log in have ended
53) 21:56:32 Counter: Close the semaphore
54) 21:56:42 Basile: Basile is logged out
55) 21:56:58 Macrakis: Macrakis is logged out
56) 21:57:11 Ahmed: Ahmed logged out
57) 21:57:18 Ragon: Ragon is logged out
58) 21:57:31 Brown: Brown logged out
59) 21:57:36 Counter: All users are logged out
60) 21:57:36 Counter: Close the semaphore
61) 22:00:16 Main: Semaphore deleted with status 1
62) 22:00:16 Main: End of the process
(63) Close the semaphore at 10:00:16
In the second window there will be a log(5) Creating a new semaphore at 21:51:00
(6) Zemaitis entered the system at 21:51:00
(7) Goldman logged in at 9:51:12 PM
(8) Cooke logged in at 9:51:24
(9) Kratzmann logged in at 9:51:39 PM
(10) Roentgen logged in at 9:51:47 PM
(11) Xerxes logged in at 9:51:59 PM
(12) Houseman logged in at 21:52:10
(13) Wijnschenk logged in at 21:52:18
(14) Orwell logged in at 21:52:33
(15) Gomez logged in at 21:52:49
(18) Quilty logged in at 9:53:46 PM
(20) Kelvin logged in at 21:54:00
(22) Nelson logged in at 9:54:11
(24) Ragon logged in at 21:54:23
(26) Quilty logged in at 9:54:31
(28) Williams logged in at 21:54:42
(31) Macrakis logged in at 21:54:58
(35) Brown logged in at 9:55:08
(37) Yancik logged in at 9:55:16 PM
(40) Jaynes logged in at 9:55:27 PM
(42) Rogers logged in at 9:55:34
(43) Basile logged in at 9:55:47
(46) Taylor logged in at 9:56:02 PM
(47) Ahmed logged in at 9:56:09 PM
(50) Edwards logged in at 9:56:23 PM
(52) Those wishing to log in ended at 21:56:32
(53) Close the semaphore at 21:56:32
In the third window will be a log(16) Creating a new semaphore at 21:53:46
(17) Kratzmann logged out at 9:53:46 PM
(19) Orwell logged out at 21:54:00
(21) Goldman logged out at 9:54:11
(23) Gomez logged out at 9:54:23 PM
(25) Zemaitis is logged out at 9:54:30
(27) Nelson logged out at 9:54:42
(29) Houseman logged out at 9:54:49 PM
(30) Quilty logged out at 9:54:52
(32) Xerxes logged out at 9:55:00 PM
(33) Quilty is signed out at 9:55:02 PM
(34) Cooke logged out at 9:55:04
(36) Williams logged out at 9:55:14
(38) Kelvin logged out at 9:55:17
(39) Roentgen logged out at 9:55:26 PM
(41) Jaynes logged out at 9:55:34
(44) Rogers logged out at 9:55:50
(45) Yancik logged out at 9:55:58
(48) Taylor logged out at 9:56:11
(49) Wijnschenk logged out at 9:56:15
(51) Edwards logged out at 9:56:29
(54) Basile logged off at 9:56:42 PM
(55) Macrakis logged out at 9:56:58
(56) Ahmed logged out at 9:57:11
(57) Ragon logged out at 9:57:18 PM
(58) Brown logged out at 9:57:31
(59) All users are logged off at 21:57:36
(60) Close the semaphore at 21:57:36
Now let's take a closer look at what happened. I specifically waited for the 10 allowed users to “enter” into the system to show that the semaphore value could not be less than 0. These are the users above the red line in the figure. We see that there is a break almost a minute before being allowed to log in to the next user. And that only after someone came out. I deliberately
bold the users who are logged in, and
strikethrough those who leave it. The colors are highlighted by the entry / exit pairs for each user.

As for me, an interesting picture came out.
In addition to the
Increment and
Decrement methods used in the example, you can use the wait list and methods for working with it to work with the semaphore:
- AddToWaitMany - adding a semaphore operation to the list
- RemoveFromWaitMany - remove an operation from the list
- WaitMany - wait until all operations on the semaphore are completed.
In this case,
WaitMany loops through all the tasks in the list and, after successfully completing the operation, calls the
Waitback method
WaitCompleted , which the developer must implement on his own. It is called when the semaphore has allocated a non-zero value for the operation, or the wait has timed out. The number by which the counter has been reduced is returned to the argument of this method (0 in the case of a timeout). After this method has worked, the semaphore is removed from the WaitMany task list and then the next task is carried out.
More information on semaphores in Caché can be found in the documentation (there you can also see another example of working with a semaphore) and in the class description.
Since at the time of the creation of the article, the version of Caché 2014.2 is available only on the field test portal for users on the support and participants of the academic program of InterSystems University , the links will be added as soon as the release is released.The project is on
github .
If there are comments, comments or suggestions - you are welcome. Thank you for attention!