Operating Systems Design and Implementation Notes
3. Semaphore and IPC Problems
By Jiawei Wang
Before we take a further step into Semaphore, let’s
make a conclusion of the Prev
Note:
1. The main difference between normal strict
alternation and condition variables to avoid
race conditions is that:
Condition variables make the
waiting process blocked rather than busy waiting.
Which
makes the code more efficient and also can prevent more unknown
errors.
- In the prev note. We use Mutex to prevent chaos, and using Condition Variable to guarantee sequences.
In this note. First we will introduce Semaphore.
Then we will use this new technique to solve two classical IPC
problems.
1. Semaphore
This was the situation until E. W. Dtra (1965) suggested using an integer variable to count the number of wakeups saved for future use. In his proposal, a new variable type, called a semaphore, was introduced.
In my opinion: Semaphore is a combination of both
Mutex and Condition Variables:
* A
semaphore could have the value 0, indicating that no wakeups were saved,
or some positive value if one or more wakeups were
pending.
We can do both up and down operations with semaphore.
The down operation on a semaphore checks to see if the value is greater than 0. If so, it decrements the value and just continues. If the value is 0, the process is put to sleep without completing the down for the moment.
The up operation increments the value of the semaphore addressed. If one or more processes were sleeping on that semaphore, unable to complete an earlier down operation, one of them is chosen by the system and is allowed to complete its down.
One important thing to notice is that: Both operations of incrementing the semaphore and waking up one process is indivisible, so as decreasing and sleep a process
Example: Codes/semaphore.c
#include <dispatch/dispatch.h> // use dispatch intead of semaphore on MacOSX (deprecated)
#define THREAD_NUM 4
;
dispatch_semaphore_t semaphore
void* routine(void* args) {
(semaphore, DISPATCH_TIME_FOREVER); // down
dispatch_semaphore_wait(1);
sleep("Hello from thread %d\n", *(int*)args);
printf(semaphore); // up
dispatch_semaphore_signal(args);
free}
int main(int argc, char *argv[]) {
[THREAD_NUM];
pthread_t th= dispatch_semaphore_create(2); // only allow two threads execute at the same time
semaphore int i;
for (i = 0; i < THREAD_NUM; i++) {
int* a = malloc(sizeof(int));
*a = i;
if (pthread_create(&th[i], NULL, &routine, a) != 0) {
("Failed to create thread");
perror}
}
for (i = 0; i < THREAD_NUM; i++) {
if (pthread_join(th[i], NULL) != 0) {
("Failed to join thread");
perror}
}
(semaphore);
dispatch_releasereturn 0;
}
Output:
❯ ./a.out
Hello from thread 0
Hello from thread 2
(sleep one sec)
Hello from thread 1
Hello from thread 3
In this example:
we assign two to the semaphore
variable – Only two threads are allowed to execute at any moments.
If you want to practice one more example of semaphore, you can check
Codes/semaphores.c.
Which gives us a
Log In Model in real life. and the server can only deal
with 4 users at the same time.
2. Classical IPC Problems
1. The Dining Philosophers Problem
Dinning Philosophers
Five philosophers are seated around a circular table.
Each philosopher has a plate of spaghetti. The spaghetti is so slippery that a philosopher needs two forks to eat it.
The life of a philosopher consists of alternate periods of eating and thinking.
When a philosopher gets hungry, she tries to acquire her left and right fork, one at a time, in either order.
If successful in acquiring two forks, she eats for a while, then puts down the forks and continues to think.
The key question is: can you write a program for each philosopher that does what it is supposed to do and never gets sucked
#define N 5 /* number of philosophers */
#define LEFT (i + N - 1) % N
#define RIGHT (i + 1) % N
#define THINKING 0 /* philosopher is thinking */
#define HUNGRY 1 /* philosopher is trying to get forks */
#define EATING 2 /* philosopher is eating */
int state[N];
int phil[N] = { 0, 1, 2, 3, 4 };
; /* mutual exclusion for critical regions */
dispatch_semaphore_t mutex[N]; /* semaphore per philosopher */
dispatch_semaphore_t s
void think(int i) {
int thinktime = rand() % 10 + 1;
("%d philosopher is thinking...\n", i);
printf(thinktime);
sleep}
void eat(int i) {
int eattime = rand() % 5 + 1;
("%d philosopher is eating...\n", i);
printf(eattime);
sleep}
void test(int i) {
if(state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) {
[i] = EATING;
state(s[i]);
dispatch_semaphore_signal}
}
void take_forks(int i) {
(mutex, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait[i] = HUNGRY;
state(i);
test(mutex);
dispatch_semaphore_signal(s[i]);
dispatch_semaphore_signal}
void put_forks(int i) {
(mutex, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait[i] = THINKING;
state(LEFT);
test(RIGHT);
test(mutex);
dispatch_semaphore_signal}
void* philosopher(void* arg) {
while (1) {
int index = *(int*) arg;
(index);
think(index);
take_forks(index);
eat(index);
put_forks}
}
int main(int argc, char* argv[]) {
(time(NULL));
srand[N]; /* number of threads */
pthread_t th= dispatch_semaphore_create(1); /* binary semaphore */
mutex for (int i = 0; i < N; i++) {
[i] = dispatch_semaphore_create(0);
s}
int i;
// create
for (i = 0; i < N; i++) {
if(pthread_create(&th[i], NULL, philosopher, &phil[i]) != 0) {
("Failed to create a philosopher");
perror}
}
// join
for (i = 0; i < N; i++) {
if (pthread_join(th[i], NULL) != 0) {
("Failed to join thread");
perror}
}
// release
(mutex);
dispatch_releasefor (int i = 0; i < N; i++) {
(s[i]);
dispatch_release}
}
The solution presented in philosopher.c is deadlock-free and allows the maximum parallelism for an arbitrary number of philosophers.
2. The Producer-Consumer Problem
The producer-consumer problem (also known as the bounded buffer problem).
Many processes share a common, fixed-size buffer.
Some of them, the producer, puts information into the buffer, and the other one, the consumer, takes it out.
#define THREAD_NUM 8
; // Empty slots
dispatch_semaphore_t semEmpty; // Full slots
dispatch_semaphore_t semFull
;
pthread_mutex_t mutexBuffer
int buffer[10];
int count = 0;
void* producer(void* args) {
while (1) {
// Produce
int x = rand() % 100;
(1);
sleep
// Add to the buffer
(semEmpty, DISPATCH_TIME_FOREVER); // down Empty slots, stop when semEmpty == 0 (full)
dispatch_semaphore_wait(&mutexBuffer);
pthread_mutex_lock[count] = x;
buffer++;
count(&mutexBuffer);
pthread_mutex_unlock(semFull); // up
dispatch_semaphore_signal}
}
void* consumer(void* args) {
while (1) {
int y;
// Remove from the buffer
(semFull, DISPATCH_TIME_FOREVER); // down Full slots, stop whe semFull == 0 (empty)
dispatch_semaphore_wait(&mutexBuffer);
pthread_mutex_lock= buffer[count - 1];
y --;
count(&mutexBuffer);
pthread_mutex_unlock(semEmpty);
dispatch_semaphore_signal
// Consume
("Got %d\n", y);
printf(1);
sleep}
}
int main(int argc, char* argv[]) {
(time(NULL));
srand[THREAD_NUM];
pthread_t th(&mutexBuffer, NULL);
pthread_mutex_init= dispatch_semaphore_create(10);
semEmpty = dispatch_semaphore_create(0);
semFull int i;
for (i = 0; i < THREAD_NUM; i++) {
if (i > 0) {
if (pthread_create(&th[i], NULL, &producer, NULL) != 0) {
("Failed to create thread");
perror}
} else {
if (pthread_create(&th[i], NULL, &consumer, NULL) != 0) {
("Failed to create thread");
perror}
}
}
for (i = 0; i < THREAD_NUM; i++) {
if (pthread_join(th[i], NULL) != 0) {
("Failed to join thread");
perror}
}
(semEmpty);
dispatch_release(semFull);
dispatch_release(&mutexBuffer);
pthread_mutex_destroyreturn 0;
}