Monitor programming in JR
Like I promised, I will restart to write articles now that the evaluation period is over.
After seeing how to develop using the JR programming language, we'll see now how to use monitors in JR.
Monitors provide a higher-level abstraction than semaphores and produce a better code with several advantages :
- All the synchronization code is centralized in one location and the users of this code doesn't need to know how it's implemented.
- The code doesn't depends on the number of processes, it works for as much process as you want
- You doesn't need to release something like a mutex, so you cannot forget to do it
The mutual exclusion is implicit with monitors. Only one process is allowed in the monitor, so all the method are automatically guarded with synchronization code. The synchronization between threads is made using signaling system, with condition variables. A condition variable is a kind of queue of process who are waiting on the same condition. You have several operations available on a condition, the most important is to signal a process waiting to be awaken and to wait on a condition. There are some similitudes between signal/wait operations and P and V of semaphores, but this is a little different. The signal operation does nothing if the queue is empty and the wait operation put always the thread in the waiting queue. The process queue is served in a first come, first served mode.
Now we'll see how to use them in JR.
In JR, you doesn't have directly the possibility to create monitors, but there is a preprocessor that transform a file with both JR and monitors operations into a plain JR file. This processor is called m2jr (monitor to JR) and is directly available in the JR distribution. So you only have to use the m2jr command to translate a .m file (conventional monitor extension file) into several JR files (one for the monitor and one for the condition variables). This file is normal JR with several comments to make the debugs easier (corresponding between lines in m and JR).
All the keywords of the m2jr language start with _ (underscore). To declare a monitor, it's as easily as use the _monitor keyword :
_monitor MonitorTest { //... }
To add methods to the monitor, you just have to create method prefixed with _proc :
_monitor MonitorTest { _proc void testA(){ //Some code } }
Only with that the mutual exclusion is guaranteed. Only one process is allowed into the monitor. If you want methods with a return type, you must use _return instead of return :
_monitor MonitorTest { _proc void testA(){ //Some code } _proc int testB(){ _return 1; } }
To declare condition variables, the keyword is _condvar. You don't have to initialize them, just declare them :
_monitor MonitorTest { _condvar condVar1; _condvar condVar2; }
To use global variables, you must prefix them with _var :
_monitor MonitorTest { _var var1; _var var2; }
And to make operations on condition variables, you have to use _signal and _wait methods inside a proc method :
_monitor MonitorTest { _condvar a; _condvar b; _proc void test(){ _wait(a); //Some computations _signal(b); } }
And you compile that to JR using the simple commmand :
m2jr MonitorTest.m
That will create two files (MonitorTest.jr and c_m_condvar.jr). Of course, you can use this monitor in a normal JR class, you don't have to compile classes who use monitors with m2jr, only with the JR compiler. There is just a single thing to worry about. m2jr generates a constructor that take a String in every monitors class, you when you instantiate the monitor, you have to provide a String representing its name as the first parameter. And if you want to create a new constructor in a monitor, you just have to call super with a String. We'll see an example later.
Before going further, we must have more informations about the signal operations. When writing monitors, you have the choice between several philosophies for the signaling operation :
- Signal & Continue (SC) : The process who signal keep the mutual exclusion and the signaled will be awaken but need to acquire the mutual exclusion before going.
- Signal & Wait (SW) : The signaler is blocked and must wait for mutual exclusion to continue and the signaled thread is directly awaken and can start continue its operations.
- Signal & Urgent Wait (SU) : Like SW but the signaler thread has the guarantee than it would go just after the signaled thread
- Signal & Exit (SX) : The signaler exits from the method directly after the signal and the signaled thread can start directly. This philosophy is not often used.
By default, m2jr make the compilation with SC, but you can configure it to use other philosophies. Just add the abbreviation of the philosophy (lower case) as an option to the m2jr compiler. By example, to compile using Signal & Exit :
m2jr -sx MonitorTest.m
The main differences, is that the SC create a signal stealers problem. You will quickly understand with an example. With what we know now, we can create a monitor to manage a bounded buffer :
_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; } }
A thing that some of you will certainly find weird is the loop around the wait operation. Is to avoid the signal stealers problem. If there were not while loop, imagine that situation in SC :
- The thread 1 try to make a fetch(), there is no data, so it wait for the notEmpty condition variable
- The thread 2 make a deposit(), that awake the thread 1, but it needs to acquire again the mutual exclusion.
- Before the thread 2 has acquired the mutual exclusion, the thread 3 make a fetch(), there is enough data, so thread 3 make a fetch and get the data. So now, it's empty.
- The thread 2 acquire the right to go into the monitor and get a data. But wait a minute, there is no data and it will fetch a null data or perhaps an old data still fetched depending on the current state of the buffer
To avoid that situation, you just have to wrap the wait in a while loop instead of a if and that's done ! Or you can also use SW instead of SC.
With that monitor, we can easily solve the producer and consumer problem :
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()); } }
That will provide output like that :
Consumer10 : Producer0 Consumer0 : Producer1 Consumer1 : Producer2 Consumer2 : Producer4 Consumer3 : Producer5 Consumer4 : Producer3 Consumer8 : Producer7 Consumer11 : Producer10 Consumer6 : Producer8 Consumer5 : Producer6 Consumer7 : Producer11 Consumer9 : Producer9
So it works well.
More than signal and wait operations, m2jr provide also others operations on condition variables :
- _signal_all : Awake all the process of the waiting queue. This operation is only provided in Signal & Continue mode.
- _empty : Test if the condition variable has any process waiting on it
- _wait(condvar, int priority) : Enqueue the process with the given priority. If you use that, the queue is now managed as a priority queue. You cannot use both wait without priority and wait with priority on the same condition variable
- _minrank : Return the min priority on the given condition variable. If the condition variable has no waiters, the returned given number doesn't seem anything.
With all that stuff, you can create monitors to solve almost all concurrency problems like barber shop, philosopher dinner, kwai cross or a lot of others problem.
I hope you found that post interesting. The next post about JR will be about operations and capabilities.
Comments
Comments powered by Disqus