From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.
We appreciate your patience as we improve our online experience.
From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.
We appreciate your patience as we improve our online experience.
06-02-2021 01:32 PM
Hello,
I'm looking for some advice on global variable clusters.
I am working on a program that has a fair bit of input and output data from the controller modules as well as the user interface. As of now I have grouped some sections of data into clusters, and written those clusters to global variables. From a programming standpoint this has made things simpler for me because wherever I need a piece of data I just wire the global to a bundle or unbundle and pull out or write what I need.
I have been mindful of race conditions this whole time, so I made sure that wherever I write to an element of any of my global variable clusters it is the only time I am writing to that element. Now, there are multiple places where I write to the same global variable cluster, but I am writing to different elements in that cluster.
For instance, I have a global variable cluster for the output signals sent to my cRIO that my DAQmx Write VI pulls from:
Here I am writing to one of the elements:
And here I am writing to a few other elements:
There are no instances where I am writing to the same element twice in the "Cell 1 cRIO output signals global" cluster, so it seems to me that this approach is safe. However, this is my first Labview project, and I know there are some very wise users around here who may be able to tell me if I am doing this completely wrong. I'd like to avoid having to split everything up into individual globals, but if that's what it takes then I guess it is what it is.
Let me know if you need any clarification! And thank you for your help!
Solved! Go to Solution.
06-02-2021 02:05 PM
No matter what language you are programming in, constantly reading from and writing to globals is a dangerous game to play. That being said, almost all my globals are clusters, but I use them only to hold things like configuration parameters that only get written to once (from a file, for example) and read when necessary.
There must be a better way, but I am not well-versed with cRIO, so maybe there's not.
06-02-2021 02:12 PM
@rseabeck wrote:
I have been mindful of race conditions this whole time, so I made sure that wherever I write to an element of any of my global variable clusters it is the only time I am writing to that element. Now, there are multiple places where I write to the same global variable cluster, but I am writing to different elements in that cluster.
That is a classic scenario that can and probably will lead to race conditions. Just keep in mind, even if you change only one element of a cluster, you are writing back ALL elements of the cluster. If you do these writes in two places in parallel, you cannot be sure, that both your changes will finally be part of your global cluster.
Regards, Jens
06-02-2021 02:14 PM
As stated, using globals you are flirting with diaster. They are safe if you ar eusing WORM (Write Once Read Many) globals but in your usage you are not guaranteed that you do not have a race condition and that you are corrupting your data. Globals can also be used safely if you guarantee that there is ONLY one place that writes to the global. When I say this I am not referring to multiple writers that appear to do it without conflict I mean literally only one writer.
I write very large applications and I cannot recall the last time I used a global variable. There generally better methods. Messaging between tasks using queues or notifiers for example. Definign tasks or using OOP to encapuslate your data are other options.
06-02-2021 02:19 PM
I had a cluster like that in one of my first LabVIEW projects. That is where I truly learned what a race condition was.
A better method is a functional global variable. But now just one where you read from it, change something and set it back. You need to make sure the read and write are both enclosed in the subVI. You'd have a read method for just when you want to read. An initialize where you set the value of the master cluster initially. And then a case for each element of the cluster where you update a specific element of the cluster so that in one action you read, update the element, write the cluster back to the shift register.
The problem with this is that it can get messy with bigger clusters, or more complicated clusters where you might have various types of data and would thus need multiple different inputs, or a variant input where you'd have to then convert the variant to the correct datatype.
I haven't done this, but you might want to look at or the newer map functions where you each piece of data becomes an element in the map. Or use a Data Value Reference so that you can lock access to the master cluster and update the appropriate elements of the cluster as needed.
06-02-2021 06:23 PM
I just wanted to amplify a specific detail related to what JensG69 already said:
@JensG69 wrote:
Just keep in mind, even if you change only one element of a cluster, you are writing back ALL elements of the cluster. If you do these writes in two places in parallel, you cannot be sure, that both your changes will finally be part of your global cluster.
In the screencap you posted, here is how that race condition happens. Both screencaps show you reading from the global immediately as the pictured code starts executing. Then code inside the pictured structures executes for a bit and when it's done, some cluster elements are updated. Then you write the entire cluster back to the global.
If both bits of pictured code start running at the same time, it's *very* likely that you'll retain updates from only one of them -- it will be the slower one that finished last and overwrote whatever had been updated previously by the faster one.
Really, you need to find a different way, even at the expense of extra time and learning curve. I think a Functional Global (as suggested by RavensFan) is likely to be the most beginner-friendly approach overall.
-Kevin P
06-02-2021 07:40 PM
Shameless plug: A Look At Race Conditions
06-03-2021 09:58 AM
Thank you Mark. This was very clear and concise.
I started dusting off my Core 2 notebook and I think I am going to tackle this problem with channel wires. I know I could use queues too, but I think that will get cluttered since I have queues set up for my state machines.
I appreciate the help all of you have given!
06-03-2021 10:19 AM
@rseabeck wrote:
There are no instances where I am writing to the same element twice in the "Cell 1 cRIO output signals global" cluster, so it seems to me that this approach is safe. However, this is my first Labview project, and I know there are some very wise users around here who may be able to tell me if I am doing this completely wrong. I'd like to avoid having to split everything up into individual globals, but if that's what it takes then I guess it is what it is.
That sounds like good care, but if you have several loops you'll still get race conditions. Using the mentioned Functional Global/Action Engine should take care of that. Most programs end up having just 1 loop, or atleast 1 Consumer loop doing most all of the actual work, if the cluster resides in that as a shift register all writes happens in 1 place and nowhere else. 🙂
06-03-2021 12:13 PM
Putting them in a cluster pretty much guarantees race conditions. Just move the elements out of the cluster.