UPDATE: If you want a fast playback system which can do async requests for buffers better go with a lock-free audio queue. Vrok’s backend will be changed soon to a lock-free audio queue which is the most optimal design that I found after some research.
NOTE: If you want a bug free program in a constrained amount of time, this won’t be the most optimal method; for now at least.
ANOTHER NOTE: DO NOT use common lock names, std::mutex, pthread_mutex_lock ..etc and roll out your own and use Thread Error Detectors they actually think its the standard version of these classes/functions!
After you have read the above note, you can continue reading of my experience in using something that was always considered to avoid. I did it because it did not occur to me in the first place. I did not use much of the common standards that were in use in the field. But I however used the “producer-consumer” problem to gain some ground.
I am talking about my experience while implementing the decoder-playback loop of Vrok. The loop is documented below,
mutex.lock() // work mutex.unlock()
if (atomic_check(&pause_check)) atomic_set(&pause, true) pause_mutex.lock() pause_mutex.unlock() atomic_set(&pause, false) atomic_set(&pause_check,false) mutex.lock() // work mutex.unlock()
Anyone would be implementing something like this if they were to follow a producer-consumer approach, and then I remembered that this is a player and I have to pause this from time to time, and I did not pick to just pause the output or stop the looping altogether; I chose to deadlock purposely which led to alot of problems there after, thread error detector programs like Valgrind and Intel’s Inspector XE reported deadlocks in huge amounts that I started to wonder how does it even run at all with all of these deadlocks! And there were actual deadlocks because of the “irresponsible” implementations that I have done in the pause() function.
I had to make up my mind either to keep on using what ever that I have written or go read the source of some successful non-freezing player! It was then that I remembered that I haven’t refered to any documentation nor any proper engineering approach to answer this problem. After drawing the locking diagram, I could see it in a way that I did not see before. Thread Error detectors can help you if you have used the basic elements that are given by standard thread libraries, if you roll your own you are alone! After rethinking about the design I was able to get the more serious problems resolved, problems(i.e. freezing of the control thread: Qt’s main thread) do not appear at normal playback of a song but at the end or the start of a song due to the deadlocks that I have introduced purposely, forget to release the locks in a way that it gurantees that the system resolves the deadlock it is in you will suffer endless nights thinking what you did wrong. Only later you find out that you were doing it right but not in the correct order. I still haven’t seen any documentation on how to implement/debug systems that use deadlocks as a synchronization mechanism, I’d like to see some if some of you know of any drop a comment or a mail if you know of anything that would contribute.
For people who are interested in the technical details of the implementation,
Thread/lock implementation, you need a mutex that’s unlockable from any thread(not just the locked thread). I do know of RW locks but the rwlock() and rdlock() did not suite for this purpose for me because all the existing code was written to use a single lock() function.
Decoder thread code, the implementations of rewind() and pause() are now the same because of some fixes, this was done because to keep the API stable, it’ll be changed soon.