I’m a beginner and have watched quite a few posts and talks regarding thread safety, atomic and etc but still feel confused about this topic. Please bear with me if this is like a silly question. In Timur Doumler’s talk, there is an example:
struct Synthesiser
{
std::atomic<float> level;
// GUI thread:
void levelChanged (float newValue)
{
level.store (newValue);
}
// real-time thread:
void audioCallback (float* buffer, int numSamples) noexcept
{
const float currentLevel = level.load();
for (int i = 0; i < numSamples; ++i)
buffer[i] = currentLevel * getNextAudioSample();
}
};
This use of atomic<float>
makes sense to me. Then I was thinking about the case where there are multiple parameters, and instead of put them one by one in Synthesiser class directly, I want to wrap them in a struct. From the talk, std::atomic<Widget> parameter
are likely to insert mutex inside and therefore not lock-free, but I’m not very sure about the understanding. I can think of two ways to write class Parameters
:
In the first way, make the parameter fields float and the class object atomic; in the second way, make the parameter atomic and the class object normal one as below
struct Parameters1
{
float Q {2};
float frequency {100};
};
struct Parameters2
{
std::atomic<float> Q {2};
std::atomic<float> frequency {100};
};
class PluginProcessor
{
public:
......
void setParams () // UI thread
{
Parameters1 param;
param.Q = 3.f;
param.frequency = 200.f;
params1.store(param);
params2.Q.store(3.f);
params2.frequency.store(200.f);
}
void getParams() // Audio thread
{
auto p1 = params1.load();
auto Q1 = p1.Q;
auto f1 = p1.frequency;
auto Q2 = params2.Q.load();
auto f2 = params2.frequency.load();
}
......
std::atomic<Parameters1> params1;
Parameters2 params2;
};
is any of these two certainly thread safe (no data race)? are they lock-free? I tried std::atomic<Parameters1>::is_always_lock_free
and std::atomic<Parameters2>::is_always_lock_free
and both return a true
. Can I make the statement that if all the fields of a struct is lock-free, then the struct itself is also lock-free? Or it just happen to be true only in my particular platform/hardware?
my following question is, even if the parameter struct itself is not lock free, will Parameters2 still be acceptable in my case, since it’s not changed as a whole in setParams()
(I know if I write something like params2 = someNewParams
, probably the object is left in some kind of in-between state). But here I only seem to care about its members, which are both atomic, so I guess it will be fine? I feel this is kind of like the way using juce::AudioProcessorValueTreeState
where itself is not atomic or lock-free but getRawParameterValue(StringRef)
return an atomic object.