Using Semaphores For Producer-Consumer Operations With semop()
Using a semaphore as a mutex is not utilizing the full power of the
semaphore. As we saw, a semaphore contains a counter, that may be used for more
complex operations. Those operations often use a programming model called
"producer-consumer".
A D V E R T I S E M E N T
In this model, we have one or more processes that produce
something, and one or more processes that consume that something. For example,
one set of processes accept printing requests from clients and place them in a
spool directory, and another set of processes take the files from the spool
directory and actually print them using the printer.
To control such a printing system, we need the producers to maintain a count
of the number of files waiting in the spool directory and incrementing it for
every new file placed there. The consumers check this counter, and whenever it
gets above zero, one of them grabs a file from the spool, and sends it to the
printer. If there are no files in the spool (i.e. the counter value is zero),
all consumer processes get blocked. The behavior of this counter sounds very
familiar.... it is the exact same behavior of a counting semaphore.
Lets see how we can use a semaphore as a counter. We still use the same two
operations on the semaphore, namely "signal" and "wait".
/* this variable will contain the semaphore set. */
int sem_set_id;
/* semaphore value, for semctl(). */
union semun sem_val;
/* structure for semaphore operations. */
struct sembuf sem_op;
/* first we create a semaphore set with a single semaphore, */
/* whose counter is initialized to '0'. */
sem_set_id = semget(IPC_PRIVATE, 1, 0600);
if (sem_set_id == -1) {
perror("semget");
exit(1);
}
sem_val.val = 0;
semctl(sem_set_id, 0, SETVAL, sem_val);
/* we now do some producing function, and then signal the */
/* semaphore, increasing its counter by one. */
.
.
sem_op.sem_num = 0;
sem_op.sem_op = 1;
sem_op.sem_flg = 0;
semop(sem_set_id, &sem_op, 1);
.
.
.
/* meanwhile, in a different process, we try to consume the */
/* resource protected (and counter) by the semaphore. */
/* we block on the semaphore, unless it's value is non-negative. */
sem_op.sem_num = 0;
sem_op.sem_op = -1;
sem_op.sem_flg = 0;
semop(sem_set_id, &sem_op, 1);
/* when we get here, it means that the semaphore's value is '0' */
/* or more, so there's something to consume. */
.
.
Note that our "wait" and "signal" operations here are just like we did with when
using the semaphore as a mutex. The only difference is in who is doing the
"wait" and the "signal". With a mutex, the same process did both the "wait" and
the "signal" (in that order). In the producer-consumer example, one process is
doing the "signal" operation, while the other is doing the "wait" operation.
|