Please complete the required studio exercises listed below, along with any of the (optional) enrichment exercises that interest you.
As you work through the exercises, please record your answers in a file, and upon completion please upload a file with your answers to the assignment page for this studio in Canvas.
Please make sure that the name of each person who worked on these exercises is listed in the first answer, and that you number your answers so they are easy for us to match up with the appropriate exercise.
thread_management
).
In that directory, edit a new C++ source code file (which should be named
something like thread_management.cpp
) and in it
write a main function whose signature looks like the standard (i.e.,
portable between Linux and Windows) shell (on Windows, terminal window)
program main function
entry point for C++: int main (int, char * [])
.
Please also use the #define
precompiler
directive to define a manifest constant whose value is 0, and have the main
function simply return that constant to indicate successful completion of the
program.
Implement an interrupt_flag
class and an interruptible_thread
class according to
Listing 9.9 of [Williams], and in your main function write a simple test with two threads
that validates and demonstrates the use of those features (see section 9.2.2 of [Williams] for code
examples that may be useful in doing that).
As the answer to this exercise, please show your code and describe how you tested that feature.
unsigned int n
, computes the nth
Fibonacci number, and returns its value.
Use an atomic thread_local
interrupt_flag
and the
interruptible_thread
class to make that computation interruptible (using the same strategy
as in the previous exercise, with the computation checking for interruption after each successive
Fibonacci number it computes, (and terminating if so).
Integrate that function within your code from the previous exercise so that the main function (1) launches
a std::packaged_task
with the Fibonacci number computation in several threads (each with a
different Fibonacci number - some larger and some smaller), (2) repeatedly (i.e., in a loop) does rapid timed
waiting on each of their futures (using a small wait time like a millisecond for each wait) until the first
one completes (based on the status of the future becoming ready as in section 9.2.5 of [Williams]),
(3) interrupts the other computations' threads and joins with each of them that is
still joinable, and then gets and prints out the value of the future that was seen to have completed first.
Build and run your program, and as the answer to this exercise please show your code and the output your program produced.
Implement timed waiting on a std::condition_variable
as in Listing 9.11 of [Williams], and
combine that mechanism with an additional interruption point and the timed waiting on fugures, to wait
to be signaled by the main function's thread. The event loop function should
repeatedly (until interrupted) perform very short timed waits on the condition variable and on the futures of
any Fibonacci number computations that are currently running, and in between each of them should check
whether or not it has been interrupted.
If at any point the event loop discovers that it has been interrupted, it should interrupt each currently running Fibonacci number computation, then once all have been interrupted join with each of them, and then exit.
After waiting on the condition variable, the event loop should check an atomic flag indicating whether it needs to launch a new Fibonacci number computation and if that flag is set then launch a new Fibonacci number computation using the value of another atomic unsigned integer variable (that the main function can assign before it sets the new computation flag) and clear the new computation flag.
Note that in theory this approach has a race for the value of the unsigned integer variable which could cause some requested computations not to be launched. However, we'll ignore that for this exercise, on the assumption that a person (rather than another computer program) will be providing the input that causes each computation to be launched and (1) the input speed will be slow compared to the speed of the event loop (so that the race is unlikely to happen in practice), and (2) if a computation were omitted the person could simply retry launching it.
After waiting for each future the event loop should test whether or not the future is ready (as in section 9.2.5 of [Williams]) and if it is should (1) get the value from the future and print it out, (2) remove the future from the list of futures on which it was waiting, and (3) remove the interruptible thread for that computation from the list of computations on which it was waiting.
Have the main function launch the event loop thread, and then interact with the user by repeatedly prompting (via the standard output stream) for and then reading in (via the standard input stream) a C++ stype string that is either equivalent to "stop" or is an unsigned integer value (all decimal digits) - the program should ignore any other strings and should simply re-prompt for a new input if one is seen. When the program sees an unsigned integer value it should assign it to the atomic unsigned integer variable that it uses to pass values to the event loop, and set the atomic new computation flag, and signal the condition variable on which the event loop may be waiting. When the program sees "stop" it should interrupt the event loop thread (which will then interrupt any currently running Fibonacci number computations) and then join with the event loop thread.
Build and run your program, and as the answer to this exercise please show your code and a trace of the output and input (cut and paste from the terminal window) for an illustrative run of your progtram.
std::condition_variable_any
mechanism shown in Listing 9.12 of [Williams], and use it in place of timed waiting on a
std::condition_variable
. Note that this may require signaling the condition variable,
from each Fibonacci computation when it completes, to avoid deadlock and/or unnecessary delays in when the
result of a computation is printed.Build and run your program, and as the answer to this exercise (1) show your code and (2) describe any interesting features of the implementation, or of the program's behavior using it, which you observed.
thread_local
termination flag (and a simple strategy of checking
for termination after each element it looks at) so that all threads search for the same value, and once
a thread finds the value that thread sets all of the other threads' termination flags which they each
detect and call off their searches.As the answer to this exercise please describe how you modified your solution from the previous studio, and show your code.