CSE 532S Advanced Thread Management Studio

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.


    Required Exercises

  1. As the answer to the first exercise, list the names of the people who worked together on this studio.

  2. Create a new empty directory for this studio (for example named something like 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.

  3. Write a function that takes a parameter 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.

  4. Factor out the code that can launch a Fibonacci number computation, into a separate interruptible thread for an event loop (with which the main function will communicate based on user input), and integrate interruption points with its timed waiting on a future as in section 9.2.5 of [Williams].

    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.

    Enrichment Exercises (Optional)

  5. Repeat the previous exercise, but implement the sophisticated 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.

  6. Use the thread interruption features from the previous exercises to modify your solution to the third exercise of the previous studio (CSE 532S Concurrency Design Studio) with each thread having an atomic 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.