I would generally advice against using these atomic booleans as a way to synchronise audio and ui thread. It is recommended relatively often and it is working well in maybe 9/10 (or 99/100) cases, but most of the time, you don’t think of every race condition. It is also not exactly scalable when your application grows more complex.
As a concrete situation where these booleans are most likely to fail: consider the possibility of updates from the UI happening very fast. The audio thread might overwrite the second rebuild request while performing the first (or something similar etc.)
For synchronisation, I’d recommend to look at three relatively easy patterns:
- Atomic values: good for single parameter values like gain. Everything that fits into a primitive type. Major benefit: it is very easy to use, hard to get wrong and costs are relatively low (no system calls). Don’t use atomics to pass pointers to bigger structures around, without being absolutely sure of what you are doing. The problem with pointers ultimately surfaces when you think about destruction at the correct moment (and the cherry on top: make sure the audio thread is not the one calling std::free)
- Read-Copy-Update (RCU): great pattern to sync data structures. Here is a ready to use implementation (the author also has a talk on YouTube explaining what he did). I won’t go into more detail here, as there are a lot of resources (also in this forum) on that matter. Snapshots/Include/Snapshots.h at main · PalmerHogen/Snapshots · GitHub
- RCU has a disadvantege when it comes to the “copy” step. If your data structure is big, it might be an expensive operation to maybe change a single value. This is where you might want to use a queue of updates. In essence, you tell your audio thread what exactly has to change without passing an entirely new copy. Here is a great talk on that topic: https://www.youtube.com/watch?v=lb8b1SYy73Q It is also how JUCE implemented their sampler demo: JUCE/examples/Plugins/SamplerPluginDemo.h at master · juce-framework/JUCE · GitHub The command queue downsides are, that it is only working properly with two threads: one to push changes, one to pull and execute changes. If you have two threads pushing, the execution order might get tangled and does not make sense anymore (especially with topographic changes to your structure). RCU on the other hand has the ability to report on the updater, that it was not the one committing the update (a different thread got there first), so the updater (UI) can have a second chance to repeat the RCU in it’s entirety with the additional data from the other updater.
