Locks, RAII best practice


#1

In the past I have found locks straight forward enough but I have need to pass data between Graphics and Audio Thread but I can’t in this example set a flag and poll so I need to use a lock. The operation is infrequent with little chance of contention but for app stability I think it’s necessary.

How do I set a mutex where the graphics thread is the owner of the mutex. Lock the thread whilst in the audio callback loop and occasionally lock on graphics thread (being the owner) and keep it all within an RAII style pattern?

(It’s the mutex being on the UI thread that’s causing undefined behaviour)


#2

Would a memory block be more appropriate

std::atomic<int> xFree{0};

void thread_A()
{
    while(xFree) { std::this_thread::yield(); } xFree++;
    // Do some work
    // This could be the audio loop
    --xFree;
}
void thread_B()
{
    while(xFree) { std::this_thread::yield(); } xFree++;
    // Do some work
    // This is a very short amount of work that will be triggered from UI thread
    --xFree;
}

C++ std::vector in a array of classes
#3

Have you seen Timur’s video showing how to pass data between audio/GUI threads? https://youtu.be/boPEO2auJj4


#4

I am not sure which thread you want to lock. To be clear: it is ok for the GUI thread to wait for the audio thread, but never the other way round. If you are drawing with 30+ frames per second, you provide more information than the eye can distinguish. Nobody can tell by only watching, if you missed one frame.
However, if you only miss one audio buffer, the discontinuities are extremely noticeable, not only to the trained ear.
(I guess that’s nothing new to you, sorry for stating the obvious).

The AudioProcessor class has a lock built in, that you can acquire using AudioProcessor::getCallbackLock(). So if you just want to copy from a small vector in the processor, you can use this:

GUI::paint (Graphics& g)
{
    {
        ScopedReadLock lock (processor.getCallbackLock());
        // copy your data
    }
    // draw stuff
}

This will make sure, that processBlock is not currently running, but it will also block the call of processBlock() until your scope is finished and the lock is released.
Also note, you can read from a member, where you copied the audio data beforehand. You cannot get access to the AudioBuffer used inside processBlock(), since it’s lifetime is managed outside your AudioProcessor class.


#5

That doesn’t rule out the possibility of priority inversion. If the (low-priority) GUI thread is preempted by the OS while holding the lock, you’re doomed (i.e., it would block the high-priority audio thread for an indefinite amount of time and glitch, even if the intent was to only copy a small vector).

If the OS supported priority inheritance, this would be safe, but unfortunately, neither Windows nor macOS currently does that. I usually use a lock-free FIFO or a triple buffer to do stuff like this.


#6

You are absolutely right to emphasise that. In my designs I usually push from the audio thread into a big enough buffer, so I don’t have to lock.
Also depending on the data to be copied, e.g. copying audio into an already allocated memory block is a risk that you can take, IMHO. But everybody has to decide that for themselves.
I haven’t investigated lock-free solutions yet, so if somebody wants to explain that here, I’m all ears…