Another advantage of semaphores is in situations where the developer would need to restrict the number of times an executable can execute or be mapped in memory. Let's see a simple example:
There are two processes: Producer and Consumer. The Producer inserts information into the data area; while the Consumer removes information from the same area. There must be enough space for the Producer to insert information into the data area. The Producer's sole function is to insert data into the data area. Similarly, the Consumer's sole function is to remove information from the data area. In short, the Producer relies on the Consumer to make space in the data-area so that it may insert more information, while the Consumer relies on the Producer to insert information into the data area so that it may remove that information.
To develop this scenario, a mechanism is required to allow the Producer and Consumer to communicate, so they know when it is safe to attempt to write or read information from the data area. The mechanism that is used to do this is a semaphore.
In the below sample code , the data area is defined as
http://linuxdevcenter.com/pub/a/linux/2007/05/24/semaphores-in-linux.html?page=6
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <stdio.h> #include <errno.h> #define KEY 0x100 typedef union semun { int val; struct semid_ds *st; ushort * array; }semun_t; int main() { int semid,count; struct sembuf op; semid = semget((key_t)KEY,10,0666|IPC_CREAT); if(semid==-1) { perror("error in creating semaphore, Reason:"); exit(-1); } count = semctl(semid,0,GETVAL); if(count>2) { printf("Cannot execute Process anymore\n"); _exit(1); } //get the semaphore and proceed ahead op.sem_num = 0; //signifies 0th semaphore op.sem_op = 1; //reduce the semaphore count to lock op.sem_flg = 0; //wait till we get lock on semaphore if( semop(semid,&op,1)==-1) { perror("semop failed : Reason"); if(errno==EAGAIN) printf("Max allowed process exceeded\n"); } //start the actual work here sleep(10); return 1; }
Difference Between Semaphores and Mutex
After reading though the material above, some pretty clear distinctions should have emerged. However, I'd like to reiterate those differences again here, along with some other noticeable differences between semaphore and Mutex.- A semaphore can be a Mutex but a Mutex can never be semaphore. This simply means that a binary semaphore can be used as Mutex, but a Mutex can never exhibit the functionality of semaphore.
- Both semaphores and Mutex (at least the on latest kernel) are non-recursive in nature.
- No one owns semaphores, whereas Mutex are owned and the owner is held responsible for them. This is an important distinction from a debugging perspective.
- In case the of Mutex, the thread that owns the Mutex is responsible for freeing it. However, in the case of semaphores, this condition is not required. Any other thread can signal to free the semaphore by using the
sem_post()
function. - A Mutex, by definition, is used to serialize access to a section of re-entrant code that cannot be executed concurrently by more than one thread. A semaphore, by definition, restricts the number of simultaneous users of a shared resource up to a maximum number
- Another difference that would matter to developers is that semaphores are system-wide and remain in the form of files on the filesystem, unless otherwise cleaned up. Mutex are process-wide and get cleaned up automatically when a process exits.
- The nature of semaphores makes it possible to use them in synchronizing related and unrelated process, as well as between threads. Mutex can be used only in synchronizing between threads and at most between related processes (the pthread implementation of the latest kernel comes with a feature that allows Mutex to be used between related process).
- According to the kernel documentation, Mutex are lighter when compared to semaphores. What this means is that a program with semaphore usage has a higher memory footprint when compared to a program having Mutex.
- From a usage perspective, Mutex has simpler semantics when compared to semaphores.
A Worker-Consumer Problem
The worker-consumer problem is an age old scenario that has been used to justify the importance of semaphores. Let's see a traditional worker-consumer problem and its simple solution. The scenario presented here is not too complex.There are two processes: Producer and Consumer. The Producer inserts information into the data area; while the Consumer removes information from the same area. There must be enough space for the Producer to insert information into the data area. The Producer's sole function is to insert data into the data area. Similarly, the Consumer's sole function is to remove information from the data area. In short, the Producer relies on the Consumer to make space in the data-area so that it may insert more information, while the Consumer relies on the Producer to insert information into the data area so that it may remove that information.
To develop this scenario, a mechanism is required to allow the Producer and Consumer to communicate, so they know when it is safe to attempt to write or read information from the data area. The mechanism that is used to do this is a semaphore.
In the below sample code , the data area is defined as
char buffer[BUFF_SIZE]
and buffer size is #define BUFF_SIZE 4
. Both Producer and Consumer access this data area. The data area's limit size is 4. POSIX semaphores are being used for signaling.#include <pthread.h> #include <stdio.h> #include <semaphore.h> #define BUFF_SIZE 4 #define FULL 0 #define EMPTY 0 char buffer[BUFF_SIZE]; int nextIn = 0; int nextOut = 0; sem_t empty_sem_mutex; //producer semaphore sem_t full_sem_mutex; //consumer semaphore void Put(char item) { int value; sem_wait(&empty_sem_mutex); //get the mutex to fill the buffer buffer[nextIn] = item; nextIn = (nextIn + 1) % BUFF_SIZE; printf("Producing %c ...nextIn %d..Ascii=%d\n",item,nextIn,item); if(nextIn==FULL) { sem_post(&full_sem_mutex); sleep(1); } sem_post(&empty_sem_mutex); } void * Producer() { int i; for(i = 0; i < 10; i++) { Put((char)('A'+ i % 26)); } } void Get() { int item; sem_wait(&full_sem_mutex); // gain the mutex to consume from buffer item = buffer[nextOut]; nextOut = (nextOut + 1) % BUFF_SIZE; printf("\t...Consuming %c ...nextOut %d..Ascii=%d\n",item,nextOut,item); if(nextOut==EMPTY) //its empty { sleep(1); } sem_post(&full_sem_mutex); } void * Consumer() { int i; for(i = 0; i < 10; i++) { Get(); } } int main() { pthread_t ptid,ctid; //initialize the semaphores sem_init(&empty_sem_mutex,0,1); sem_init(&full_sem_mutex,0,0); //creating producer and consumer threads if(pthread_create(&ptid, NULL,Producer, NULL)) { printf("\n ERROR creating thread 1"); exit(1); } if(pthread_create(&ctid, NULL,Consumer, NULL)) { printf("\n ERROR creating thread 2"); exit(1); } if(pthread_join(ptid, NULL)) /* wait for the producer to finish */ { printf("\n ERROR joining thread"); exit(1); } if(pthread_join(ctid, NULL)) /* wait for consumer to finish */ { printf("\n ERROR joining thread"); exit(1); } sem_destroy(&empty_sem_mutex); sem_destroy(&full_sem_mutex); //exit the main thread pthread_exit(NULL); return 1; }
Conclusions
We've explored the possibilities of different varieties of semaphores, as well as the differences between semaphores and Mutex. This specific knowledge could be helpful to developers in migration between System V and POSIX semaphores and when deciding whether to use Mutex or semaphores. For further details on the APIs used in the above example, refer to relevant the man pages.http://linuxdevcenter.com/pub/a/linux/2007/05/24/semaphores-in-linux.html?page=6
No comments:
Post a Comment