As with message queues, the syntax and manipulation of semaphores is somewhat complex, making them a prime candidate for incorporation into a C++ class. A semaphore class would define the relationships between semaphore data and the functions ( methods ) that manipulate this data. A declaration of a simplified semaphore class called SSemaphore [5] is shown in Figure 7.10.
[5] The name SSemaphore (with the extra 'S' ) was chosen to minimize any conflicts with existing semaphore definitions.
Figure 7.10 Header file for a basic semaphore class.
File : SSemaphore.h
/*
A VERY simplified semaphore class for use in a std UNIX
environment. See the text for instructions on how to use
this class. Copyright (c) 2002 J. S. Gray
+
Exit codes for class operations:
1 - Semaphore allocation failure 2 - Unable remove semaphore
3 - Unable to LOCK semaphore 4 - Unable to UNLOCK semaphore
10 5 - Failure on wait for ZERO 6 - Unable to assign value
7 - Unable to return value
*/
#ifndef SSemaphore_h
+ #define SSemaphore_h
#define _GNU_SOURCE
#include
#include
#include
20 #include
#include
#include
#include
using namespace std;
+
class SSemaphore {
public:
SSemaphore ( ); // Constructor
~SSemaphore( ); // Destructor - remove semaphore
30 int P( ); // LOCK (decrement semaphore)
void V( ); // UNLOCK (increment semaphore)
int Z( ); // WAIT while semaphore is NOT 0
void Put( const int ); // Assign a value to semaphore
int Get( ); // Return value of the semaphore
+ private:
#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
// definition in
#else
union semun { // We define:
40 int val; // value for SETVAL
struct semid_ds *buf; // buffer for IPC_STAT, IPC_SET
unsigned short int *array; // array for GETALL, SETALL
struct seminfo *__buf; // buffer for IPC_INFO
};
+ #endif
union semun arg; // For semctl call
struct sembuf zero,lock, unlock; // hoo ha's for P,V & Z operations
int semid; // ID of semaphore
pid_t my_pid; // PID of creator
50 };
#endif
As defined, the SSemaphore class creates a private semaphore set with a single element. There are seven public methods and six private data members in the class. The functionality of each method is shown in Table 7.10.
Table 7.10. SSemaphore Class Methods.
|
Method Name |
Explanation |
|---|---|
|
SSemaphore |
This is the class constructor. This method assigns the proper values to the zero , lock , and unlock sembuf structures and saves the PID of the calling process. Additionally, it generates the private, single element semaphore and sets it to 0. |
|
~SSemaphore |
This method removes the semaphore from the system if the calling function is the process that generated the semaphore. |
|
P |
This method atomically tests and decrements the semaphore. It blocks if the semaphore is 0. |
|
V |
This method increments the semaphore. |
|
Z |
This method tests whether or not the semaphore is at 0. If it is not at 0, it blocks. |
|
Put |
Put assigns a value to a semaphore. |
|
Get |
Get returns the current value of a semaphore. |
The C++ code that implements the semaphore class is found in the program file SSemaphore.cxx (Program 7.5). As shown, the code is bare boneslittle is done to handle errors, and only basic semaphore functionality is addressed.
Program 7.5 Program code for the semaphore class.
File : SSemaphore.cxx
/*
SSemaphore implementation - Copyright (c) 2002 J. S. Gray
*/
#include "SSemaphore.h"
+ // Generate a private semaphore
SSemaphore::SSemaphore( ){
zero.sem_num = 0, zero.sem_op = 0, zero.sem_flg = SEM_UNDO;
lock.sem_num = 0, lock.sem_op = -1, lock.sem_flg = SEM_UNDO;
unlock.sem_num = 0, unlock.sem_op = 1, unlock.sem_flg = SEM_UNDO;
10 my_pid = getpid( );
if((semid = semget( IPC_PRIVATE, 1, 0660 )) == -1 ){
exit( 1 );
}
Put( 0 ); // Default - set to zero @ start
+ }
// Remove semaphore if creator
SSemaphore::~SSemaphore( ) {
if ( getpid( ) == my_pid )
if ( semctl( semid, 0, IPC_RMID ) == -1 )
20 exit( 2 );
}
// LOCK semaphore
int // Atomic test & decrement
SSemaphore::P( ){
+ if ( semop( semid, &lock, 1 ) == -1 )
exit( 3 );
return 0;
}
// UNLOCK semaphore
30 void // Increment semaphore
SSemaphore::V( ){
if ( semop( semid, &unlock, 1 ) == -1 )
exit( 4 );
}
+
int // Wait for semaphore to be 0
SSemaphore::Z( ){
if ( semop( semid, &zero, 1 ) == -1 )
exit( 5 );
40 return 0;
}
// Assign value to the semaphore
void
SSemaphore::Put( int const value ){
+ arg.val = value;
if ( semctl(semid, 0, SETVAL, arg ) == -1 )
exit( 6 );
}
// Return value of the semaphore
50 int
SSemaphore::Get( ){
int sem_value;
if ((sem_value=semctl(semid, 0, GETVAL, 0)) == -1 )
exit( 7 );
+ return sem_value;
}
To use this class, the files SSemaphore.h and SSemaphore.cxx should reside locally. The SSemaphore class is compiled into object code with the command line
linux$ g++ SSemaphore.cxx c
At the top of the source file that uses a SSemaphore object, add the statement
#include "SSemaphore.h"
to make the class definition available to the compiler. When compiling the source file, include the message queue object code file
linux$ g++ your_file_name.cxx SSemaphore.o
In 1965 Dijkstra presented what is now considered to be a classic synchronization problem involving a group of dining philosophers. In brief, the group of philosophers is sitting around a table. Each engages in the activity of thinking and eating . To eat, the philosopher must obtain the forks on his or her left and right. Both forks are needed for dining, and once obtained, neither fork is released until the philosopher is done. For N philosophers, there are N forks (not 2 x N ). Clearly, if all the philosophers are to eat, some sort of synchronization of their activities is needed.
We can use the recently presented SSemaphore class to implement a naive solution to the dining philosophers' problem. In essence, each fork will be represented by a single binary semaphore. If a philosopher can obtain both the left and right fork (think semaphore), he or she will eat for a random number of seconds, and when done, return the forks. If either instrument is not available, he or she will wait. Keep in mind that this solution has a very basic flaw. Should all the philosophers pick up their left fork at the same time, we would have deadlock. This could occur, as each left fork is also the right fork of the philosopher to the left. With every philosopher waiting for his or her right fork (the left fork of the philosopher on his or her right), no progress can be made. Program 7.6 implements our less-than -perfect solution.
Program 7.6 A rudimentary dining philosophers' solution using semaphore objects.
File : p7.6.cxx
/*
The dining philosophers
*/
#include
+ #include "SSemaphore.h" // Our basic semaphore class
const int MAX = 5;
SSemaphore Forks[MAX];
void Philosopher( int );
void Eat_It( const int,const int, const int );
10 int
main(int argc, char *argv[] ) {
int i;
if ( argc < 2 ) {
cerr << "Usage: " << argv[0] << " secs_to_wait " << endl;
+ return 1;
}
for( i=0; i < MAX; ++i )
Forks[i].Put(true);
for(i = 0; i < MAX; ++i )
20 Philosopher( i );
sleep(atoi(argv[1])); // Parent process waits a bit
return 0;
}
void
+ Philosopher(int number ){
if (fork() == 0) { // Run in the child
int left, right;
srand(getpid( ));
left = number;
30 right= (number+1) % MAX;
do {
cout << "A. P" << number << " is thinking
";
sleep(rand( ) % 3 + 1); // Take a while to THINK
cout << "B. P" << number << " ASKS to eat with forks "
+ << left << " & " << right << endl;
Forks[left].P( ); // Acquire left fork
Forks[right].P( ); // Acquire right fork
Eat_It(number, left, right);
Forks[right].V( );
40 Forks[left].V( );
} while( true );
}
}
void
+ Eat_It(const int number, const int left, const int right) {
cout << "C. P" << number << " is EATING with forks "
<< left << " & " << right << endl;
sleep(rand( ) % 3 + 1); // Take a while to EAT
cout << "D. P" << number << " is now DONE with forks "
50 << left << " & " << right << endl;
}
As written, the program expects the user to pass an integer command-line value that will be used for the number of seconds the program should run. A value of 10 seems to produce a reasonable amount of output. In line 7 of the program, an array of five private semaphore objects is instantiated . This array represents the five forks. The loop at line 17 sets each of the semaphores to true (fork available). This is followed by a second loop that calls the Philosopher function MAX times. Upon each invocation, the Philosopher function generates a child process to carry on the activities of the philosopher. The philosopher's activities consist of thinking for a random amount of time and eating. To eat, the left and right fork (semaphore) must be acquired . When both semaphores have been procured, the Eat_It function is called where, for a random amount of time, the eating activity is carried out. While the child processes carry on their activities, the parent process sleeps for a period (see line 21). When the parent process exits, the destructor for the semaphore objects is called. The child processes exit as they encounter an error condition when the attempt to access a removed semaphore.
Figure 7.11 shows a typical run when the value 10 is passed to the program. To save space, the output is displayed as two columns .
Figure 7.11 10 seconds of output from the dining philosophers' program.
linux$ g++ p7.6.cxx SSemaphore.o -o p7.6 A. P2 is thinking D. P0 is now DONE with forks 0 & 1 linux$ p7.6 10 A. P0 is thinking A. P0 is thinking B. P1 ASKS to eat with forks 1 & 2 A. P1 is thinking C. P1 is EATING with forks 1 & 2 A. P2 is thinking B. P3 ASKS to eat with forks 3 & 4 A. P3 is thinking C. P4 is EATING with forks 4 & 0 A. P4 is thinking B. P2 ASKS to eat with forks 2 & 3 B. P1 ASKS to eat with forks 1 & 2 D. P4 is now DONE with forks 4 & 0 C. P1 is EATING with forks 1 & 2 A. P4 is thinking B. P3 ASKS to eat with forks 3 & 4 C. P3 is EATING with forks 3 & 4 C. P3 is EATING with forks 3 & 4 B. P0 ASKS to eat with forks 0 & 1 B. P0 ASKS to eat with forks 0 & 1 D. P1 is now DONE with forks 1 & 2 B. P2 ASKS to eat with forks 2 & 3 A. P1 is thinking B. P4 ASKS to eat with forks 4 & 0 C. P0 is EATING with forks 0 & 1 D. P1 is now DONE with forks 1 & 2 D. P3 is now DONE with forks 3 & 4 A. P1 is thinking C. P2 is EATING with forks 2 & 3 D. P3 is now DONE with forks 3 & 4 A. P3 is thinking A. P3 is thinking B. P1 ASKS to eat with forks 1 & 2 C. P0 is EATING with forks 0 & 1 B. P4 ASKS to eat with forks 4 & 0 C. P2 is EATING with forks 2 & 3 D. P0 is now DONE with forks 0 & 1 D. P2 is now DONE with forks 2 & 3 D. P2 is now DONE with forks 2 & 3 B. P3 ASKS to eat with forks 3 & 4
![]()
Programs and Processes
Processing Environment
Using Processes
Primitive Communications
Pipes
Message Queues
Semaphores
Shared Memory
Remote Procedure Calls
Sockets
Threads
Appendix A. Using Linux Manual Pages
Appendix B. UNIX Error Messages
Appendix C. RPC Syntax Diagrams
Appendix D. Profiling Programs