CS444
Handout: Semaphores, Monitors
Semaphores
were invented in 1965 by Dijkstra. A semaphore is a system object with some sort of
identifier, here “sem”.
The sem
object has a count associated with it. The two important operations on sem
(after its creation) are (Tanenbaum, pg. 110):
To make a real system, we also need to be able to
create a semaphore with a certain initial count: sem = createsem(initcount).
POSIX semaphores: sem_init(…)
Tanenbaum, pg. 112: This program assumes semaphore creation
somehow automatically happens, but in real life we need to do a system call to
create a semaphore. Here is that program
rewritten to use POSIX semaphores. It is
still incomplete because of the thread creation calls, as well as the various
unimplemented functions (produce_item, etc.).
/* needs various headers, including <semaphore.h> */
sem_t mutex,
empty, full; /* struct type sem_t
is defined in header */
int main()
{
if (sem_init(&mutex, /*process-local*/ 0, /*init count*/ 1) < 0)
perror("Failed
to create semaphore 1");
return 1;
}
if (sem_init(&empty,
/*process-local*/ 0, /*init count*/ N) < 0)
perror("Failed
to create semaphore 2");
return 1;
}
if (sem_init(&full,
/*process-local*/ 0, /*init count*/ 0) < 0)
perror("Failed
to create semaphore 3");
return 1;
}
thread_create(producer,
...);
thread_create(consumer, ...);
getchar();
/* block main thread */
/* here, could bring down threads and destroy
semaphores, but exit will do it for us */
}
/* producer() and
consumer() are close to Tanenbaum, pg. 112 */
/* Note: each system call should have its return
value tested as show above for sem_init
*/
void producer()
{
int item;
while (TRUE) {
item = produce_item();
sem_wait(&empty);
/* claim empty spot */
sem_wait(&mutex);
insert_item(item);
/* insert, protected by mutex */
sem_post(&mutex);
sem_post(&full);
/* signal filled-in spot */
}
}
void consumer()
{
int item;
while (TRUE) {
sem_wait(&full);
/* claim spot in buffer */
sem_wait(&mutex);
item = remove_item();
/* remove, protected by mutex */
sem_post(&mutex);
sem_post(&empty);
/* signal empty spot */
consume_item(item);
}
}
Monitors:
Packages of code with their own special mutex
ensuring that only one thread at a time can execute inside it. Other threads wait for their turn. Not available in C or C++.
Example of Monitor in Java. In
Java, we have the “synchronized” keyword to cause an object to have a mutex that ensures that only one thread at a time can
execute in any of its synchronized methods.
Thus its fields can be protected from simultaneous access by multiple
threads.
public class OurMonitor0 { //
this is a monitor
private
int buffer[] = new int[N];
// queue in ring buffer
private
int count = 0, lo = 0, hi = 0;
public
sychronized boolean insert(int val) {
if (count == N) return false; // fail if full
buffer[hi] = val;
// enqueue val, step 1
hi = (hi + 1) % N; // enqueue, step 2
count++;
// enqueue, step 3
return true;
}
// return positive
number, or -1 if nothing is there
public
synchronized int remove() {
int val;
if (count == 0) return –1;
val =
buffer[lo]; // dequeue val, step 1
lo = (lo + 1) % N; // dequeue, step 2
count--;
// dequeue, step 3
return val;
}
}
Note:
if your methods are static and
synchronized, you get a mutex in the class instance to protect them. Note
this is a different mutex from any object mutex, so static and non-static synchronized methods are
not coordinated.
Full-featured
monitors also provide appropriate blocking capability, more on this next time.