Extend this incomplete C-program. As you can see, the program has one king thread and N servant threads, but they don’t really do anything yet. Implement the following using one or more semaphores:
sleep()
function) for a random amount of time (a few seconds).printf()
) together with their servant ID.Take note of these things as well:
rand()
function to get random numbersREVISIT, WITH A TWEAK Change the previous king-servant setup with a single change: now, the king does explicitly wait for the greetings of all his servants before he goes back to sleep.
Think about if you need additional semaphores/mutexes for this and why.
In the prime-number exercises in the previous labs, we were actually implementing the basics of a producer-consumer problem, without really knowing it! In our example solution, we bypassed the shared memory issue by assigning each producer (the calculatePrimes
function) a separate block of memory only they could use/access to place their results. As you know now, this is highly inefficient and a waste of memory. As such, it’s time to make our prime-number into a proper producer-consumer setup with a single shared memory array of a limited size!
We will do this in several steps, starting with a somewhat sub-optimal mutex-based solution and ending up with a more complex semaphore-based one!
Start from the provided solution to the last prime-number exercise (6_5_3_primethreads.c
). Change it so:
results
arrays (note: this single array will still be quite large, say 20000 integers).printPrimes()
function that acts as the consumer. Use pthread_create
in main
to start a single instance of the consumer. Make sure this function prints newly produced primes as soon as possible/as soon as they’re put in the shared array (meaning: we’re no longer waiting for all the producers to be done before starting to print the results in main()
).For this, you should use a single mutex to prevent the producers/consumers from having race conditions on the single shared array.
write_index
variable to manipulate the shared arrayNote: in this setup, the primes will no longer be printed in-order from smallest to largest. That’s fine ;) Can you explain why though??? And can you imagine a possible solution where they -are- still printed in-order?
One thing you might/should have noticed in the previous exercise, is that it’s no longer easy to determine when the consumer should stop printing/when it has printed all produced primes.
In the original solution 6_5_3_primethreads.c
, we had the producers write the number 0
at the end of their result array to signal they were done (because 0
is not a prime). In the new setup however, we can’t use this anymore, because not all producers end at the same time, and so new “real” primes might be written after a 0
from the first finished producer!
As such, we need a new way to determine when we’re done: keep a count of producers that are done. Then, the consumer can check if this “done count” is equal to the number of producers. If so, it can safely exit without having to rely on the 0
sentinel value.
Keeping the “done count” thread safe can be done in two main ways. Choose one:
sem_getvalue()
function. Look up online what that does and how to use it).In the previous two versions, we had only a single consumer. To get maximum performance, we of course would like to not just have multiple producers, but also multiple consumers!
Extend your previous solution to:
printPrimes
consumer threads (ideally more!), that all read from the shared array and print the produced primes. Of course you need to make sure no primes are printed twice (= thread-safe access to the shared array from all consumers)!!!Note: this can again be done in several ways. The simplest/best way however, doesn’t even require you to define a new mutex here; you can just re-use the one from the V1 above. Tip for this: previously we just had a global write_index, now we also need another type of global index ;)
Up until now, you’ve used (should have been using?) mainly mutexes to access a single (huge) shared array. Because the array is so large, we didn’t have the typical mutex problems like busy waiting, because there was always room for newly produced primes to be placed in the shared memory (unless we had very high limits for the prime generation).
To forcibly show the usefulness of semaphores when we don’t have “unlimited” shared memory, you will now adapt your previous code to:
Tip: These changes shouldn’t be too large (though also not tiny ;)). Nor should they be very different from the producer-consumer semaphore examples shown in the course text.
Note: think about if you still need a mutex inside of your semaphore(s) as well. Why (not)? Look up the discussion on “binary semaphores” in the course text. Is this the same/a similar situation or not?
Note: this exercise is a mandatory part of Assignment 3. While the assignments are individual, you CAN (and should) collaborate on this exercise! The solution will be posted only after the deadline for assignment 3 however.