Write a C or C++ program using POSIX threads and mutexes and condition variables , which implements a solution to the Media Playback Control Problem (described below) that is free from deadlock.
The Media Playback Control Problem
Consider a system with three control threads and one player thread. The player thread plays back a media stream, consisting of some digital medium such as digital audio or video (or, in our case, text). The player has two modes: playing, and stopped. The player thread maintains a current media position, which changes periodically when the player is playing, and can be affected by commands such as Rewind(). The player accepts the following commands:
- LoadFile(const char *filename) — Prepares the player to playback the file whose name is passed in. Stops the player if currently playing.
- Stop() — Stops the player if currently playing.
- Start() — Starts the player if currently stopped.
- Rewind() — Sets the position of the player to time 0.
- SeekTo(int byteoffset) — Sets the position of the player to the closest word boundary past the given byte offset from the start of the file.
- SetRate(float rate) — Sets the playback rate: 1.0 is forward at normal speed, -1.0 is reverse, 0.5 is forward at 1/2 speed, etc.
While playing, the player thread reads one word from the file, prints it, then sleeps until the next word should be displayed, and so on. Each word should be displayed on a separate line of output. (Words are white-space-separated runs of characters.) At a rate of 1.0, the player should print one word per second. At a rate of -1.0, the player prints one word per second, but proceeding backwards through the file.
For this assignment, the player plays back text files, displaying one word per second at normal rate (1.0). You can read the entire file at the start of playback or , for extra credit, stream the file from disk using read-ahead to prevent “dropouts” (missed drawing deadlines). The read-ahead should use one or more additional threads to do the actual io, and there should be synchronization between the display and io threads. You can send a new I/O request when the buffer reaches a “low-water” mark, and put the io thread to sleep when the buffer reaches a full-water mark. Or use your own design for this producer-consumer problem. “Much respect” if you can get the buffering to work when playing backwards.
The control threads generate commands for the player thread. You can think of each control thread as corresponding to a separate remote control. There are two kinds of control threads. The first kind waits for keypresses, and issues commands to the player thread based on the key pressed:
- ‘ ‘ (blank) — toggles the player’s mode between ‘playing’ and ‘stopped’
- ‘r’ — rewinds the player
- ‘s’ — prompts the user for a time, then seeks the player to that time
- ‘l’ — prompts the user for a file, then loads the file into the player
- ‘t’ — prompts the user for a rate, then uses it to set the player’s rate
Two other control thread are “autonomous” controllers, simulating another user issuing commands. One of the autonomous controllers periodically issues a Stop() command to the player, while the other autonomous controller thread issues a Start() command every so often. Each of these autonomous threads should sleep for a random time interval of between 5 and 30 seconds before issuing each command.
Your program should implement a function for each command (Start(), Stop(), etc.). These functions will be called by the control threads to control the player. Shared, global variables for the state, rate, position, etc. of the player thread are accessed by these routines, as well as the player thread routine. You must sychronize access to these variables to prevent race conditions, using mutexes (and optionally condition variables).
You program should first initalize the shared variables, then start the player thread (in “stopped” mode), then the control threads. The “keyboard” control thread should also have a command that will cleanly terminate all of the threads and exit the program.