Skip to content

Race Conditions and Functional Global Variables in LabVIEW

by Brian on August 19, 2011

Do you know the party game, “telephone”?  It’s where a group gets in a circle, and someone whispers a statement to the person next to them, who in turn whispers it to the person next to them, until the message gets all the way around the circle.  Invariably, the message gets corrupted along the way, and the statement at the end has lost all of its original meaning.  I find it both funny and sad.

The same thing seems to have happened with some information on race conditions and functional global variables in LabVIEW, so I want to try to clear it up.

It started earlier this week when I found an NI-internal document that’s used for code reviews, which said…

Functional Global Variables

A way to avoid race conditions associated with local and global variables is to use functional global variables. Functional global variables are VIs that use loops with uninitialized shift registers to hold global data.

· …

· The FGV eliminates race conditions

Whoa!  Given no context, that last statement is just plain wrong.

An internal document is one thing, but I’ve also heard this echoed by at least one customer in the past month, and also in an informal conversation here at NI.

What’s going on???  I decided to find out.  Keep reading to understand more about race conditions and how this game of “telephone” progressed to where we are today.

The LabVIEW Style Checklist

It gets worse.  Here’s a snippet from NI’s “LabVIEW Style Checklist” in the LabVIEW 2011 Help, where I’ve bolded part of the last, disturbing paragraph…

Avoid using local variables when you can use a wire to transfer data. Every local variable that reads the data makes a copy of the data. Use global and local variables as sparingly as possible.

Details

You can use global and local variables to write VIs efficiently, but use global and local variables as sparingly as possible. If you misuse or abuse global and local variables, particularly with array data types, the memory usage of the VI increases and the performance is affected.

You can encounter race conditions when reading from and writing to local or global variables in the same application. Race conditions are difficult to debug because there is no data dependency between different instances of the same local or global variable on the block diagram.

Consider using functional global variables instead of global variables. Functional global variables do not create extra copies of data and allow certain actions, such as initialize, read, write, and empty. They also eliminate race conditions.

Really?  Really?  FGV’s magically eliminate race conditions?  I’m starting to get upset.

With the help of Steve K., an NI AE Specialist, I started working my way upstream to find the source of this misinformation.  Steve pointed me to some NI training course material and some of the LabVIEW help.  I’ve started working my backwards through the circle to find out what was originally said, and how it got misinterpreted along the way.

LabVIEW Core 1 Training Course

Here, I think, is the ultimate source…  It’s our LabVIEW Core 1 training course.

The good news is that the material in the training class is written well and accurately; it’s at the beginning of the circle, before the message got corrupted.

I’m going to borrow some of the Core 1 content to explain some of the concepts and issues.  Lesson 9 is about using local and global variables to communicate information between parallel loops. It then covers the concept of race conditions, and uses an example like this…

Core 1 Example

Consider what happens if the loop operations happen in the following order:

  1. Loop 1 reads the global variable.
  2. Loop 2 reads the global variable.
  3. Loop 1 increments the value it read.
  4. Loop 2 increments the value it read.
  5. Loop 1 writes the incremented value to the global variable.
  6. Loop 2 writes the incremented value to the global variable.

In this case, the count variable gets incremented by one, even though it was accessed twice.  Just to be clear, LabVIEW could also choose to execute the same VI like this…

  1. Loop 1 reads the global variable.
  2. Loop 1 increments the value it read.
  3. Loop 1 writes the incremented value to the global variable.
  4. Loop 2 reads the global variable.
  5. Loop 2 increments the value it read.
  6. Loop 2 writes the incremented value to the global variable.

And since this is all happening in parallel threads, possibly on two separate cores, the exact interaction between loops 1 and 2 can vary from iteration to iteration of each loop.

This is “correct” behavior from LabVIEW’s perspective; the diagram doesn’t specify any order dependency between the two loops, so it doesn’t impose any.

But probably, you want the “read, increment, write” to happen atomically—as if it were a single operation that could never be interrupted.  This is such a common thing to need to do that CPUs actually have this built in as a machine instruction.  It’s very important for avoiding race conditions across multiple CPUs in a system.

Critical Sections

LabVIEW Core 1 introduces the concept of critical sections. A critical section is a piece of code that accesses a shared resource that must not be concurrently accessed by more than one thread of execution.

The “read, increment, write”, then, is a critical section.  You want to ensure that those three things happen atomically—without interruption.  Continuing with the material from Core 1…

One way to protect critical sections is to place them in subVIs. You can only call a non-reentrant subVI from one location at a time. Therefore, placing critical code in a non-reentrant subVI keeps the code from being interrupted by other processes calling the subVI. Using the functional global variable architecture to protect critical sections is particularly effective, because shift registers can replace less protected storage methods like global or single-process shared variables. Functional global variables also encourage the creation of multi-functional subVIs that handle all tasks associated with a particular resource.

Great.  FGVs are a good way to protect a critical section, which in turn can eliminate race conditions.

Action Engines

I want to be especially clear that the FGV must contain the critical section for it to eliminate race conditions.  In our example above, the critical section was “read, increment, write”.  If you had an FGV that just did “read” and “write”, it wouldn’t protect the critical section.

Action engine is a term that was created to describe a special kind of FGV where it does more than just read and write to data storage.  An “action” can be anything.  In our example, it’s “read, increment, write”.  We could have other actions, such as modifying a file, updating a database, performing I/O; anything we wanted to protect from executing in multiple threads at the same time.

So unlike simple variables, which can just read and write, an action engine can perform a more meaningful action which includes the entire critical section that you want to protect.

We cover this in the LabVIEW 2011 Help, in a section on “Using Local and Global Variables” in a discussion about “read, modify, write”…

Race conditions also occur when two operations are trying to update a global variable in parallel. In order to update the global variable, an operation reads the value, modifies it, and writes it back to the location. When the first operation performs the read-modify-write action and the second operation follows after, the outcome is correct and predictable. When the first operation reads and then the second operation reads, both operations modify and write a value. This action causes the read-modify-write race condition and produces invalid or missing values.

You can avoid race conditions associated with global variables by using functional global variables. Functional global variables are VIs that use loops with uninitialized shift registers to hold global data. A functional global variable usually has an action input parameter that specifies which task the VI performs. The VI uses an uninitialized shift register in a While Loop to hold the result of the operation. Using one functional global variable instead of multiple local or global variables ensures that only one operation executes at a time, so you never perform conflicting operations or assign conflicting values to stored data.

Here, the word “operation” is more or less a critical section—a “read, modify, write” action.  I am a little troubled by the first sentence of that second paragraph just above…

You can avoid race conditions associated with global variables by using functional global variables.

If you remove the context of a “read, modify, write” critical section, it will be misinterpreted.  Here’s an example…

LabVIEW Core 3 Training Course

As we sometimes do in our training material, we revisit earlier topics when they’re relevant to new material.  The LabVIEW Core 3 class includes this paragraph in a discussion about Information Hiding… [emphasis is mine]

Functional Global Variables

You can use uninitialized shift registers in For or While Loops to hold data
as long as the VI never goes out of memory. The shift register holds the last
state of the shift register. A loop with an uninitialized shift register is known
as a functional global variable. The advantage of a functional global variable
over a global variable is that you can control access to the data in the shift
register. Also, the functional global variable eliminates the possibility of
race conditions
because only one instance of a functional global variable can
be loaded into memory at a time.

Oops, we misplaced a bunch of context about critical sections and actions.  FGVs can protect critical sections, but they do not inherently eliminate race conditions.

Summary

I might summarize like this…

If you have an FGV which is an action engine which protects a critical section, you eliminate race conditions associated with that critical section.

I might also simplify it to this…

Functional Global Variables are a good way to protect critical sections.

I suppose I should have started with a discussion about whether you even need to use global or local variables.  If you don’t have information that needs to be shared across threads, you probably shouldn’t be using them.  All of this is discussed better in our Core 1 training material.

Speaking of which, I like to think that someone who had gone through our training courses would have known the caveats that go along with the statements above that talk about how FGV’s eliminate race conditions.  If you haven’t taken our training, I encourage you to visit http://www.ni.com/training/, and I bet you’ll find some courses that will be valuable.

Thanks for reading this far.  I’d be curious, if you’re willing to confess in the comments section below, if my information about FGVs, race conditions, and action engines is new to you and if it’s helpful, or if you think there’s anything unclear or something I’ve forgotten to cover.

From → Programming

22 Comments Leave one →
  1. Justin Goeres permalink

    >> I find it both funny and sad.

    I put the mix at 90% funny.

  2. Jim Carmody permalink

    I confess to programming in LabVIEW for several years where that would have been new to me. I think it’s new to many (seasoned) programmers so it’s helpful to have folks educating the masses about this.

    It makes me think of this (from the forums):

  3. crelf permalink

    Ha! Functional globals eliminate race conditions? What a laugh! That’s what datasocket binds to FP controls and indicators are for!

  4. @Justin, I find it <50% funny.

    @Jim, not sure what happened, but your link didn’t make it through.

    @crelf, you are not helping. But we’re glad you’re here anyway.

  5. crelf permalink

    @Brian, You’re welcome 🙂

  6. ~Norm Kirchner permalink

    I sincerely believe that the rampant use of FGV and action engines in LabVIEW should cease.

    As with globals, they should be used as a last ditch effort and instead replaced with appropriately handled and accessed referenced sessions, however you choose to accomplish this (SingleElementQueues, ExtensibleSessionFramework, etc)

    The use of an FGV/Action engine reduces the number of VI on disk to manage at the cost scalable code.

    Example: How do most people make a timer in LV? Make an action engine.
    How do most people deal with having a second independent timer? Add extra non timer related session handling mechanisms within the action engine. Which in some cases can cause a significant re-rewrite. (sounds similar to a program that worked great w/ local variables for a while until it didn’t)
    Instead, had the developer actually used a session based timer from the get go, that could truly operate in parallel with no inter dependencies or blocking from the other timer, there would have been no extra coding needed, they just make a new timer session.

    Just like many new ideas, to adjust our thinking requires a bit of a force feeding to ourselves, but I Strongly recommend that LabVIEW developers out there (y’all), abandon FVG/AE’s for a period to see what you come up with that helps you better solve your needs.

    *And most importantly help reduce those pesky race conditions.

    ESF – http://tinyurl.com/3lzn3l4

    • Nancy permalink

      Norm, I mostly agree ;).

      For many applications that I run into, the singleton FGV is sufficient. The customer is relatively new to LabVIEW, moving to that intermediate status. Storing data in an FGV is an important foundational concept and can be key to a successful first LabVIEW application.

      Then it’s a straightforward step to a referenced solution. We talk about this in the Advanced Architectures in LabIVEW course. We illustrate with DVRs. Simply code a Create and Destroy. Then take each case of the FGV/AE and create a separate VI with the DVR wrapper.

      Now I do agree with you when I see a large application that exclusively uses FGVs for data persistence and data communication – ugh!!!

  7. Yair permalink

    For my money, the biggest problem with this has been language. People use several terms interchangeably to refer to several things, with those terms somewhat overlapping and causing a lot of confusion. Your post, the comments here and, to a degree, the documentation you quoted, all contribute to this problem. I myself have freely used these terms randomly on many occasions.

    Strictly speaking, the basic get-set variant should NOT be called an FGV, as it doesn’t provide any functionality which global variables don’t have. It could be called a USR global, which would refer to its most common implementation, but that term is not that common. Most people do call it an FGV, which is what leads to problems, especially when inexperienced users hear the veterans loudly proclaims “Globals are EVIL. Use FGVs/action engines”. I have seen my fair share of people who then go and use the USRs thinking they’re better (and yes, I have been guilty of this myself).

    If you look at the style checklist quote you were angry with, you will see that in that case you were the one who actually broke the phone. They say “Functional global variables do not create extra copies of data and allow certain actions, such as initialize, read, write, and empty”, which is correct, if by “FGV” you mean “action engine”, which is obviously what whoever wrote this meant. In that sense, they are correct that the FGV’s they’re talking about would help with race conditions.

  8. crelf permalink

    Globals aren’t evil, USR Globals (LV2Style) aren’t evil, Action Engines aren’t evil, (insert other technique we’re talking about) aren’t evil. Each of the technologies have their strengths and weaknesses,.and should all be taught to the masses – nothing should be off the table, but everything should have it’s pros and cons listed eloquently (and practically), for the less-savvy programmer to ber able to make an informed choice.

    • ASTDan permalink

      I am trying to move away from saying any technique is “bad” I agree every thing has its strengths and weakness, and nothing should be off the table. For example I have recently come back to local and global variables in certain situations after a period of believing that they were the devil . They can be a very effective tool, and are easy to use.

  9. First of all, I love the comments! Thank you all for participating.

    I think Yair hit the nail on the head when he talked about terminology–USR global, FGV, Action Engine. Because we haven’t been careful with the terminology, one person could argue they are all the same, and another could argue they are all different.

    The example in the checklist (initialize, read, write, empty) might describe an action engine, but it says nothing about whether you have a critical section in there being protected, so it also says nothing about whether there’s a race condition being avoided.

    I might change my last summary to this…

    Non-reentrant VIs are a good way to protect critical sections.

    By so doing, I might have strayed too far from the original topic of wanting to share and protect data. But, it’s the most accurate in terminology.

    Thanks for your thoughts Norm. I might add on to Chris’ thoughts by saying that there’s a learning path to this part of LabVIEW, and I don’t want to throw out all of the intermediate steps/solutions that we’ve gone through to get the point where we understand Extensible Session Frameworks and the like. I’m all for clarifying the steps along the way, and moving people quickly up the learning curve.

  10. LabBEAN permalink


    strike this from the LV Core 3 excerpt » “The shift register holds the last state of the shift register.”

    Would someone clarify whether the term FGV refers to a global memory accessor with *added* functionality OR VIs that *function* like global variables (e.g. LV2 — see below)? Who coined the term?

    As a takeaway, while I don’t use globals, could we define the terms? Is the following accurate?

    LV2: 2 states: read & write specified

    FGV: multiple states: read, write specified, & write constant (e.g. empty, initialize)

    AE: FGV with added functions: protect critical section, open connection, access shared resource, close connection

    -Jason

  11. Nancy permalink

    @Yair @LabBEAN @crelf @Brian

    There is a bit of history to the naming conventions. We had many names floating in the LabVIEW universe. VIGs was another one. As I’ve taught courses throughout the US I discovered that preferred naming conventions were somewhat geographically dispersed. AE’s were used in the North East, VIGs were used in the Midwest. I attributed this to the Select Integrators in the area, dispensing their vast wisdom, and naming conventions.

    When I was editing a course many years ago I ask NI what term should be used for the “LabVIEW 2 Style Global” and the reply was FGV.

    But I concur that we have two use cases for the FGV. One is simple data storage and the other is protecting sections of code. The data storage use case includes the simple “read” or “write” as separate methods. Though the race condition is not eliminated, the FGV design pattern allows the programmer to validate the data before it is stored. Do we ever use the term AE in our course material? Would be nice to clearly delineate these, or other, use cases with the appropriate terms.

    So why not put a VI wrapper around the Global Variable? It’s the non-reentrant VI wrapper that protects against the race condition. Of course the Global Variable would need to be part of a class or library and subsequently scoped to Private. Mercer led an interesting discussion here

    Perhaps “Race Conditions and FGVs Part 2” should follow shortly.

    • Yair permalink

      > It’s the non-reentrant VI wrapper that protects against the race condition

      How is it going to do that? The only way to fully protect against race conditions is to make every read-write action atomic – if you want to read and then write back, you have to lock the resource somehow. I don’t see how wrapping the global is going to help with that, unless you move all the modifying actions into a single VI, and then you end up with an AE, so you might as well use a shift register to hold the data (or, alternatively, a DVR. But then you don’t need the global either).

  12. Yes, @Yair, you understand correctly. The non-reentrant VI protects a critical section, and one of the rules for critical sections is that the shared resources always have to be protected. You can’t access the shared resource from an unprotected place. The easiest way to do this, then, is to isolate the access to the shared resource to the single non-reentrant VI. You don’t actually “share” the global, except through it’s non-reentrant VI.

    I think @Nancy’s point is that the shared resource could be implemented with a global variable if you wanted. The implementation should be hidden from the users of the shared resource.

  13. Broken Arrow permalink

    I’m surprised that no one mentioned, as an alternative to a Global, LV2, FGV, AE, or DVR, that you keep eveything wired .

  14. Ha! Thanks, @Broken Arrow. You are right that using dataflow is the best way to avoid these kinds of race conditions.

    I should have prefaced my initial post that all this was about how to communicate among parallel processes. If everything can be sequential, then dataflow is (probably) the best way to go.

  15. Sam Taggart permalink

    This hits the nail on the head. I recently helped a coworker out on a project that was full of global variables. I beat him over the head about using global variables. His solution: Put everything in FGVs. The only thing that changed was that instead of global reads/writes everywhere there were FGVs with get/sets everywhere.

    I couldn’t help but shake my head at that one.

    • I’m curious, @Sam Taggart, what led your coworker down that path? Did you say something, or did he read something, that led him to mistakenly pursue the FGV approach?

      I’m trying to understand what we could do better to help people from going down the wrong path.

      • alexwk permalink

        This is obviously an important issue, to ensure the integrity of information shared by several concurrent threads of code. I am rather perplezed by the necessity of the term “Action Engine” (AE) as distinct from “Functional Global Variable” (FGV). A FGV is described, here: https://decibel.ni.com/content/docs/DOC-2143 – Basic Functional Global Variable Example, as “Functional Globals are VIs that allow controlled access to data or resources, often allowing various actions to be performed”. In fact, doesn’t the term “Functional” in FGV precisely imply some functionality beyond just reading and writing? So, it seems to me that this term “Action Engine” is unnecessary, superfluous, and really only contributes to further confusion. Or did I miss something? Thx.

        • Yair permalink

          > Or did I miss something?

          No, the confusion exists partly due to people using a jumble of different terms to refer to the same or to different things. That’s exactly what we were talking about above.

          In any case, since there is no canonical source for what each term actually means, assuming that “functional” implies additional functionality is just that – an assumption which you might make and someone else might not. For instance, like Nancy said, some people might simply add data validation to the write case and leave it at that.

  16. Neb permalink

    One aspect of AE’s not concidered above is their ability to be accessed across process contexts and across platforms.

    Using VI serve Call by ref we can invoke the AE operating on one node from another. This is particularly useful when attpemting keep multiple associated values syncronized.

    We would have to bend over backwards to make any of the other data sharing techniques mentioned above do anything similar.

    Neb

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: