1
votes

I'm having a tricky time understanding how to alternate control between two processes using semaphores. Here's a contrived example of the process handling code.

int pid = fork();

if (pid) {
  int counter = 0;
  while (true) {
    counter += 1;
    printf("P%d = %d", pid, counter);
  }
} else {
  int counter = 0;
  while (true) {
    counter += 1;
    printf("P%d = %d", pid, counter);
  }
}

I was expecting the above code to run in parallel, but it seems like control flow continues instantly for the forked process and only later resumes for the parent process.

This is fundamentally breaking my existing code that uses a semaphore to control which process can act.

int id = get_semaphore(1);
int pid = fork();

if (pid) {
  int counter = 0;
  while (true) {
    sem_wait(id);
    counter += 1;
    printf("P%d = %d\n", pid, counter);
    sem_signal(id);
  }
} else {
  int counter = 0;
  while (true) {
    sem_wait(id);
    counter += 1;
    printf("P%d = %d\n", pid, counter);
    sem_signal(id);
  }
}
  • The sem_wait helper just subtracts 1 from the semaphore value and blocks until the result is > 0 (uses semop under the hood).

  • The sem_signal helper just adds 1 to the semaphore value (uses semop under the hood).

I'd like the code to alternate between the two processes, using sem_wait to block until the other process releases the resources with sem_signal. The desired output would be:

P1 = 0
P0 = 0
P1 = 1
P0 = 1
...

However, because of the initial execution delay between the processes, the child process takes the available semaphore resource, uses it to print a number, then restores it and loops — at which point the resource is available again, so it continues without ever waiting for the other process.

What's the best way to prevent a process from using resources if it released them itself?

2
You need two semaphores and one 'executeNow' unit. Pass the unit back-and forth between the processes. - ThingyWotsit

2 Answers

1
votes

it seems like control flow continues instantly for the forked process and only later resumes for the parent process

That is because stream IO buffers the output on stdout until either

  • the buffer is full
  • fflush() is called on stdout
  • a newline (\n) is encountered

In your program, each process will fill a buffer before sending its contents to stdout giving the appearance of one process running for a long time, then the other. Terminate the format strings of your printf statements with \n and you'll see behaviour in your first program more like you expect.

I am not sure why your semaphore thing isn't working - I'm not very knowledgeable about system V semaphores but it seems like a red flag to me that you are getting the semaphore after you have forked. With the more common POSIX semaphores, the semaphore has to be in memory that both processes can see otherwise it's two semaphores.

Anyway, assuming your get_semaphore() function does the right thing to share the semaphore, there is still a problem because there is no guarantee that, when one process signals the semaphore, the other one will start soon enough for it to grab it again before the first process loops round and grabs it itself.

You need two semaphores, one for the parent and one for the child. Before the print each process should wait on its own semaphore. After the print, each process should signal the other semaphore. Also, one semaphore should be initialised with a count of 1 and the other should be initialised with a count of 0.

1
votes

Semaphores have two general use cases. One is mutual exclusion and the second is synchronization. What's been done in your code is mutual exclusion. What you actually want is synchronization (alternation) between the parent and child processes.

Let me explain a bit:
Mutual exclusion means that at any time only once process can access a "critical section" which is a piece of code that you want only one process/thread to access at a time.Critical sections generally have a code that manipulates a shared resource.

Coming to your code, since you have used only a single semaphore, there is no guarantee as to the "order" in which each process is allowed to enter the critical section. ex: sem_wait(id) from your code can be executed by any process and it's not necessary that the two processes should alternate.

For process synchronization (more specifically alternation), you need to use two semaphore one for parent and another for child.

    Sample code:
    int pid = fork();

    int parent_sem = get_semaphore(0);
    int child_sem = get_semaphore(1);

    if (pid) {
       int counter = 0;
       while (true) {
           sem_wait(child_sem);
           counter += 1;
           printf("P%d = %d", pid, counter);
           sem_signal(parent_sem);
       }
     } else {
       int counter = 0;
       while (true) {
            sem_wait(parent_sem);
            counter += 1;
            printf("P%d = %d", pid, counter);
            sem_signal(child_sem);
       }
     }

You need to initialize one semaphore (in my case child) to 1 and the second one to zero. That way only of the two processes get to start while the other enters into wait. Once child is done printing, it signals the parent. Now child's semaphore value is zero so it waits on wait(child_sem) while the parent that was signaled by the child executes. Next time, parent signals child and it executes. This continues in alternating sequences and is a classic synchronization problem.