Do we need to use std::atomic for panel theme globals?

Does anyone know of an open source plugin that makes significant use of std::atomic ? In particular I am looking for examples of atomic initialization for all types as well as more complex types than int, float, bool, etc.

1 Like

I don’t have an answer to your query, but when I want to see examples of how other plugins work I often perform a global search across the entire library of open source Rack plugins. You can clone the library repository from here: GitHub - VCVRack/library: Database for the VCV Library and then run:

git submodule update --init --recursive

to download the source code for all the plugins to your working copy.

2 Likes

Thanks. Good to know how to download everything. I usually manually download specific plugins, after someone recommends it. And then I do global searches within that plugin source.

1 Like

some of the squinky labs plugins use it quite a bit. Mainly the multi-threaded ones like SFZ Player and Colors.

1 Like

Thanks. I’ll take a look at those tomorrow.

After looking at some examples of std::atomic usage as well as how various people handle panel themes (dark, light, etc.), I’m wondering if I am overcomplicating this issue. What do you think?

I am trying to manage my panel themes via a few global variables that any Meander module instance can set and all Meander module instances read. The issue is whether I really need to do anything with these variables to make them not result in strange artifacts if one module changes the globals while another module is reading the globals. I have seen absolutely no issues with the multi-instance Meander testing even though I do nothing special to protect from the above.

Plugins seem to approach this in a lot of different ways, including: do nothing special, use std::atomic, use mutex, use locks, use customized thread management and probably more.

So, what do you think? Is it necessary to protect a few global variables from write/read synchronization issues?

Thanks for any feedback.

TL;DR – I think “don’t worry about it” is a great idea, and doing anything “fancy” is overkill for this use case.

Slightly longer: Multi-threaded programming is very difficult, and very few people have to deal with it much. I good general rule is for multi-threaded programming is “don’t do it unless you have to”.

Of course audio is one place where you do have to do it. Luckily in VCV it’s pretty rare. There are several threads in VCV, and you do need to pay attention to it, but in general all UI runs on “the UI thread” and all processing runs on one of the DSP threads. VCV’s parameters are the only place a typical plugin shares data between threads – the UI changes the parameters and the audio threads read them. This is all designed by the wizard of VCV to be thread safe.

But sometimes you need to or want to share other data between threads. Then you may get into trouble – or not.

Typical issues one might run into are:

  • If one thread writes to a variable when another one reads it, in some cases the read value will be indeterminate / corrupt. This is simple atomicity.
  • In a very tight loop an optimizing compiler might never look at the actual variable, and instead rely on a register value that it thinks is the same, but it’s not.
  • Logic errors where one thread changes multiple shared variables, and a different thread reads them while they are in an inconsistent state. This is another, much more complex issue around atomicity.
  • If a UI thread and an audio thread use a mutex or other lock to avoid the above issues, it will almost certainly introduce a “priority inversion” or some other condition that will cause pops and clicks.

All of these issues are tricky and difficult to test for. Luckily many are easy to avoid in VCV modules.

For issues 1, atomicity of a single variable read, is not usually an issue with VCV. Intel CPUs all have a coherent memory architecture where this can’t happen is most common cases. Older macs with Power PC did not have coherent cache memory, so you needed to use special instructions in this case. I don’t know what the deal is with ARM cpus. But in any case, VCV only runs on Intel CPUs, so as long as you follow the rules you are ok. I don’t remember the rules, but I think it’s about splitting large variables across multiple cache lines?

But for a real belt-and-suspenders solution to #1, use an atomic variable. Do I always do this? No.

Number 2 – the tight loop – doesn’t happen easily in VCV. There’s no place in a VCV module where you can do this, or at least you would have to really go out of your way to do this. Outside of VCV, when something like this happens a sleazy fix is to declare the variable “volatile”, but it’s much more “proper” to use std::atomic.

For number three the classis solution is to use a lock to prevent this. But we will soon see that this is a terrible solution for audio. Better solutions are a) avoid this if at all possible, b) use “lock free” programming techniques. This is very difficult, and something of a black art.

Number 4 – never, ever grab a lock in the audio thread. It will inevitably block, causing an underflow. I wrote about this here a few years ago: Demo/efficient-plugins.md at main · squinkylabs/Demo · GitHub. But the internet is full of this advice, just google it.

To me, you only need to worry about these issues if a) they are likely to happen, and b) something really bad might happen. Neither of these is true for a global to select a theme.

So, what about this simple case – a global variable for a panel theme? I wouldn’t worry because:

  • It will almost always work perfectly on an Intel CPU.
  • It’s only going to hit case 1, above, none of the others.
  • You will be doing it so infrequently that it’s unlikely to matter anyway.
  • If by some chance it worked incorrectly it wouldn’t be the end of the world
5 Likes

Thanks for your thoughtful reply. This is in line with what I was thinking, but I am no VCV Rack expert, particularly regarding threads. I will wait and see if anyone else has a contrary view and if not, I will rename this topic, mark your reply as the solution and move on.

1 Like

One of the many ways to spot a fool: someone who claims to be a thread expert.

1 Like

Hah, I have been down various threading paths too many times to consider myself as competent nor very many others as experts.

It is funny though that in my CUDA and DirectCompute GP-GPU parallel processing R&D projects, I figured out how to handle thousands of parallel threads sharing data. But, I was no expert :wink:

haha - wise words :wink: [ just edited to fix typo ]

1 Like