Locking and the fantastic world of multithreading


#1

It’s come the time for my app (which is a daw) to support automation mechanisms.
At the moment everything is based on the ValueTree class: exactly like in the Introjucer there’s a Project with Items (that are wrapped around ValueTree), and every parameter (for example, an amplitude fader) has it’s own property in an Item.
In order to make automations work I’ve created an Automation class (which is wrapped around an Item, with automation points).
My question now is this:
since everything is based on the ValueTree class (which isn’t thread-safe), how can I safely update the gui?
If I do something like this in my processBlock callback

float automationValue = getCurrentAutomationValue(); //this is the value for the automation at the current transport sample
channelTree.setProperty(Ids::amplitudeFader, automationValue, nullptr); //set the value for the fader

I’ll get the gui of the fader moving (since the graphical fader is linked to the ValueTree using ValueTree::Listener), but I’ll need locking.
And obviously the audio will stutter.
If I don’t lock I’ll stumble into a Jules’ comment which basically says: “You need to lock”.

What would be the best approach for this?
Thank you for reading!


#2

Do it using the AsyncUpdater don’t lock if you can avoid locking, do that. I use the AsyncUpdater with the Atomic class (don’t know if that’s a good idea but…)


#3

Thank you very much, atom! AsyncUpdater did the job.


#4

Yes, but bear in mind you can’t adjust a value in one thread, then - however you trigger it - access that same value in another thread.

I’ve been mulling over using values more, but since anything ‘useful’ I intend to do will involve threads, I’m not sure how. The Vinn has some nice example code of lock-free cross-thread updating, but it steers away from the simplicity of ValueTrees quite a bit.

Maybe there’s a need for cross-thread ValueTrees. A useable method may be for each thread attaching to the value to make it’s own lock-free ‘pipe’ to set and get values, each essentially keeping an updated copy?

Bruce


#5

The components in Juce can be put together, with a thread queue (such as the one in DSP Filters) thrown in, to achieve lock-free updating. This solution works as follows:

  1. Master object holds the authoritative ValueTree and provides a multi-threading API

  2. Master object API includes a Listener abstract interface for receiving change notifications

  3. When another thread wants to get a read-only copy of the ValueTree, it calls a function of the Master object. This function takes the thread, its associated thread queue, and a pointer to a Listener derived object. The function returns a reference to a duplicate ValueTree which is owned by the associated thread.

  4. When the application desires to change a value, it calls a function on the Master object with the key / new value pair. The implementation queues an asynchronous change function call which executes on the thread that owns the Master object. The change function stores the new value in the value tree and then queues an asynchronous change function call for all threads which have registered to receive read-only copies of the ValueTree. The read-only copies are changed on the thread that owns the copy.

Using this system, a GUI control changes its appearance only in response to thread queue / listener notifications. So when a user manipulates a slider, for example, the change makes a round trip through the Master object and back to the message thread before the screen updates.

The consequence of this technique is that the read-only copies will usually lag a little bit behind time-wise from the actual value. In practice, this is not a problem.


#6

That sounds good. Although, there’s a cross-thread lag when a sub-thread needs to know a value, alleviated I suppose by regular checking (assuming there was a way to skip a full update if the master is unchanged - I use an atomic version number for that exact reason).

It solves the problem with my implementation - that the sub-threads need to be polling regularly to keep the queue of changes flushed out.

Could it be integrated into juce ValueTrees, do you think?

Bruce


#7

In my opinion ValueTree is fine the way it is - concurrency features should always be built on top of robust single threaded objects. Furthermore since the needs of every application are so wildly different when it comes to multithreaded implementations, in practice it is difficult to choose a “most appropriate” implementation for library code like Juce.

The best we can hope for is to provide the building blocks, and leave it to developers to put them together in the most relevant fashion.