When someone categorically states that globals are bad, they are of course oversimplifying the explanation, probably so that you will leave them alone so they can get some work done. Similarly, the statement that globals are great, otherwise NI wouldn't have them in LabVIEW, fails to tell the whole story. The two common problems that globals will cause in an application are race conditions and performance problems. These problems are pretty easy to diagnose and relatively easy to fix once you understand them.
A race condition occurs when more than one piece of code needs to update a global and there is no way of ensuring that they take turns and leave the global in a valid state. As an example, let's look at the case when a global string is used to log important messages for the user of your VI. To add to the log, you read the global string, append your text, and write the updated string back to the global. There is no problem with this usage until you copy the code and add it to something that may run in parallel with the original.
Since the problem this causes may not be obvious, the race condition occurs when two pieces of code want to log a message at roughly the same time. The first piece of code reads the log -- copying it to its diagram wire. Then the second code reads the log string to its wire. Now both pieces of code append their message to the original log string. The problem is that neither of the log strings contains both messages. The two pieces of code will now write to the global, one at a time, and only one of the two messages will have been added. The other lost the race. As you might imagine, this bug can be in the application a long time, and you will only occasionally lose a message, so they are often some of the sneakiest bugs to track down.
The solution is to control the access, making the code take turns so that the log is written by the first before the second reads it. There are multiple ways of doing this, but the easiest is to build a subVI. The message to append is a parameter, and the subVI diagram does the read-modify-write. It is important that you do not change the VI to be reentrant or you have reintroduced the race condition, and it is important that the VI is used throughout the application, as this VI running in parallel with other code directly updating the global still has a race condition. In fact, another improvement is to change the subVI to use a shift register or a local variable to store the log message and remove the global variable entirely. With this change, all code must call the subVI to update the log since they have no other way of accessing it.
It is also possible to use globals in ways that greatly slow down your VI's execution. This is very different from a race condition in that the program executes correctly, but it takes longer than it should to complete. For an example, let's look at an array of constants needed throughout your application. You read the data from a file, a database, or wherever, and write it to the global early in your VI. Numerous parts of your application read the global data and index to get the needed constant. As long as the table of constants is relatively small, maybe around 1,000 elements, this approach works fine. But then you load up 1,000,000 elements ... and your program slows to a crawl.
It may not be obvious, but each read of a global copies the data, places it on the wire, and sends it to the next node. It has no parameters telling it which values you are interested in, so it reads the entire global. When this code is placed in a loop, it has no way of knowing if the global data is the same as last read or if other code has modified it, so it reads the entire global each and every time the global terminal executes. You may be thinking -- "That's dumb. My code is obviously only interested in one or two values, so why doesn't the LabVIEW compiler do something smarter?" Good question, but sort of a complicated one as well.
The quick answer is that the LabVIEW compiler has to be pretty defensive -- globals being globally accessible, and LabVIEW being a parallel language. At any point in time, another VI could come into memory that writes to the global. If a VI were compiled in a special way to try and lock out writers while your indexing, searching, or other downstream code runs, it could easily cause a deadlock in your application. In case deadlock is a term you aren't familiar with, it means that your application is hung and will never finish -- a VERY BAD thing. More precisely, this happens when two pieces of code need to lock two globals before running. Each piece of code will lock one of the globals, then wait forever for the other piece of code to release the global it needs. So this is one of those slippery slopes where it seems so easy for the LabVIEW compiler to help out and magically make this more efficient, but it opens the door to a much bigger problem -- deadlocks. Fortunately, there is a very good solution that will actually make the code easier to read at the same time it restores the performance.
To speed this up, make a subVI that takes the parameters for indexing the global and returns the data. The subVI diagram reads the global, indexes and returns only the data you asked for. This avoids putting lots of data on wires all over your application and will reduce the size of your application at runtime, but it can be further improved. Since the data is still in a global location, LabVIEW still has to be worried about other pieces of code changing it, so the subVI is still reading the entire global onto its wire each time it runs. By moving the data storage to be private to the subVI, this need is removed. Specifically, if the subVI has a loop with an unwired shift register, this data cannot be updated by anything but the right hand shift register terminal, and can only be read from the left hand terminal. This limited access allows LabVIEW to avoid the copy of the data, and the index can now work directly on the data and return only the element you are interested in. After adding the subVI and shift register, it takes less time to read from the 1,000,000 element global than it took to read from the 1,000 element global the initial way.
You've probably already noticed that the solution to both of these problems is pretty much the same. You can even add to this technique to return a particular row, column, statistical data, or any other function of the global data and the inputs to the VI. To do this, add an enum or ring selector for the operation to perform, along with inputs and outputs needed by each additional operation. If many operations are added, it may be a good idea to add a wrapper VI for each operation with just the inputs and outputs needed, and with a constant for the operation. This also allows you to build a good name, description, and icon for the operation on the global. For an example of a more capable global, look at <labview directory>/examples/general/globals.llb/Smart Buffer Example.vi
- Simple datatypes with simple access patterns work great with normal globals.
- Complex access patterns can cause race conditions.
- Complex datatypes or large arrays may cause performance problems.
- Functional (LabVIEW 2 Style) globals can be used to solve both of these problems