Obtaining Locks on Audio Thread

I have read and watched much excellent discussion on the topic of recommended data structures (i.e. ValueTrees & ValueTreeObjectList) and how they may be shared between threads. This, of course, inevitably leads to consideration of prohibitions on what can and cannot (or rather should and should not) be done on real-time threads (i.e. the audio thread).

I think I have just about grasped it, and could probably do a poor man’s job of setting it down here, but in the interests of time and space I will forego that and get right to my question.

One of the takeaways I have from @dave96’s ValueTree discussion (@ 36:20) is that we should avoid creating locks on the audio thread. This is re-iterated in his later talk with @fr810 when we are encouraged to (in some situations) use try_lock instead of obtaining a full lock, and then we are further advised (@ 42:50) to ensure we use a SpinLock rather than a regular std::mutex.

All good so far, but then I wanted to see an example and started poking around @dave96’s GitHub repositories to see if I could find anything and stumbled upon this which includes the following:

void PitchDetectorComponent::processBlock (const float* inputChannelData, int numSamples)
{
    const ScopedLock sl (detectorLock); // [1] <<< I thought we shouldn't do this...?
    pitchDetector.processSamples (inputChannelData, numSamples);
    pitch = pitchDetector.getPitch();
}

…in which we can see that the first thing that happens in processBlock() is to create a ScopedLock (which for completeness is defined in the corresponding header file as a CriticalSection).

I don’t want to put @dave96 on the spot (I guess I already did that, though…), but I did rather think that following the breadcrumbs of a single individual might help me end up in the same place and this apparent conflict has thrown me for a loop. I totally understand the nature of examples as well as how best practice is formulated over time (that particular file was last modified 4 years prior to the first presentation I referred to above), but to help my tired old brain I’d like to know:

  • Would the current recommendation would be not to obtain the ScopedLock within processBlock() but rather consider attempting a try_lock on a SpinLock? (For my particular use case I had determined after watching the second presentation and it’sfollowing partner that this would be right for me).
    …or…
  • Is there something I have missed or misunderstood in all I have recently consumed that means this is actually a correct approach for this particular use case?

Thanks so much in advance, and thanks also to @dave96, @fr810 and all the others too numerous to mention who have debated, presented and documented this topic at significant length for the enormous educational benefit of those who, like me, have come to the game later.

Based on a very brief look, I suspect it’s fine in this context because this is not taking place on the audio thread. The class in question is a component, and the lock is likely made to be used with some worker thread, not the audio thread. A bit misleading as the function is called processBlock, but so long as it isn’t being called from the audio thread, no issues here.

1 Like

Ah! - that would make sense.

And if I hadn’t been completely led astray by the name of the method, I would probably have arrived at that conclusion. Especially since the signature doesn’t reflect that of AudioProcessor.processBlock()

Thanks for helping me see the obvious, @Fandusss. I don’t know how long it would have taken me to realize without your observations :slight_smile:

1 Like

“Don’t use locks” is a very easy thing to throw out in the air, but people rarely go into the full gory details of what the alternatives to using locks are. Lock free programming can get very complicated and is easy to get wrong.

In an ideal world, Juce would have some kind of fully fleshed out mechanism to help with that. Just telling people to use for example AbstractFifo isn’t very useful advice. If you mention that, you should also provide some working code for how exactly it would be used in a particular situation. What data do you and even can put in that? Do you need several FIFOs instead of just one? And so on.

5 Likes

“Gory details” is one way of putting it, indeed, @xenakios.

As you will see, I am relatively new to Juce and have been intrigued (although not surprised, given the problem space) at just how many considerations must be juggled to be sure one plays by the rules that are expected of a real-time process. My learning curve has been exacerbated by having been in the enterprise software space for so long that it is over 25 years since I touched C++ (in which time I forgot much and, boy, has it changed!). “Real-time” is a term bandied around with far looser connotations in the business world!

Fortunately, though, I stumbled across @dave96’s talks as well as the many fine in-depth conversations on this forum. Regarding your point about providing examples, I found the pair of videos found here and here to be particularly instructive about how to select the right approach depending on your use case, as well as providing rough outlines of code to implement them…

What makes RT hard to learn is that lots of material you can find on the web is purely wrong.

Don’t trust anybody! :laughing:

2 Likes

Hmmm - that advice sounds a little suspect… :grinning:

2 Likes