Functional global variable: Difference between revisions
New page: Functional globals (LabVIEW 2 style globals) refer to the same code construct in LV. Most people in the mailing list refer to these structures as LV2 globals because this was the version o... |
No edit summary |
||
Line 1: | Line 1: | ||
Functional | Functional Globals are VIs that allow controlled access to data or resources, often allowing various actions to be performed. Functional Globals (FGs) most often make use of uninitialized shift registers (USRs). USRs are shift registers that do not have a wired input coming into the left side of the loop. A USR stores its data between successive runs of the VI as long as the VI remains in memory. | ||
==Alternative Nomenclature== | |||
Functional Globals are known by various names among LabVIEW programmers. | |||
*LV2 globals – because LV2 supported uninitialized shift registers | |||
*USR globals – since most FGs make use of uninitialized shift registers | |||
*Action Engines – it has been argued [http://forums.ni.com/ni/board/message?board.id=170&message.id=240328&query.id=618807#M240328] that the term Functional Globals should refer to VIs that just allow read/write access to data. Action Engines would refer to the larger set of VIs that allow some defined action on the data. | |||
The official name for these constructs in NI literature is Functional Globals, and a [http://forums.lavag.org/index.php?showtopic=2469&st=0 survey] on LAVA indicated that Functional Globals is also the most popular choice. | |||
==Structure== | |||
The most common form of a Functional Global is a [[While Loop]] with a TRUE wired to the stop condition. This forces the loop to terminate after just one execution, meaning the while loop is only used to provide a place to put USRs. A [[For Loop]] could also be used with a constant wired to the count terminal. Another method is to use a single-element queue to store the data, though this requires more mouse clicks to implement. The performance of each of these is very close and most likely will vary a little from computer to computer. | |||
[[Image:Simple_FG.PNG|frame|center|Simple Functional Global using a While Loop]] | |||
In order to specify what action to perform, an [[enumerated type]] is often used as an input to the Functional Global. Other methods are possible (Booleans, First Call?, etc.), but an enumerated type provides for a readable, extensible list of actions. Furthermore, saving the enumerated type as a typedef control makes any changes to the list automatically update wherever the Functional Global is used in the code. The ‘action’ input is fed to a case structure which contains the code to implement for each of the defined actions. | |||
For more complex situations, multiple actions can be performed on a single call to the FG. This can be a straightforward [[state machine]], or it can allow for different actions based on the data received or on the existence of resources, etc. | |||
In order to deal with varying input requirements on the different actions, one can create ‘wrappers’ around the Functional Global that allow for different inputs to be required. This also gives the benefit of being able to search your code for certain actions as you can search for a specific VI, but not a specific enumerated type’s value. | |||
==Uses== | |||
The primary use of Functional Globals is to provide data sharing across an entire project, both horizontally and vertically. Horizontal sharing means multiple top-level VIs running autonomously can all have access to the same data. Vertical sharing means that subVIs at any depth will have access to the data without requiring explicit passing of parameters through VI [[connector pane]] terminals. These are common issues in even small-sized programs. | |||
Another common use is to encapsulate some functionality, especially functionality that is linked to persistent data or resources like files or devices. This encapsulation protects the resource and provides an extensible interface for interacting with the data or resource. The encapsulation also allows you to avoid a plethora of shift registers in a single VI. You can offload the shift-register to the Functional Global, and call it when needed, without worrying about wiring the data through all your case structures. | |||
To get a better idea of what can be done with Functional Globals, here is a list of things that can be done: | |||
*Load information from a file into a FG, access it anywhere in your program | |||
*Maintain your system’s configuration parameters in one location. | |||
*Store a control reference (like a tree control) in a FG, and then define an interface to manage that control. For complex controls like trees and multi-column listboxes this can simplify your top-level code. | |||
*Timers are well implemented with FGs. You can keep the start time in the shift register, and then check the elapsed time when you call it again. | |||
*Automatically preallocate an array when the FG is first called, then use “Replace Array Subset” to efficiently manipulate the array. | |||
*Keep an ‘Undo’ list (stack) that allows you to push changes onto a stack, and pop them off when you need to undo the change. | |||
The list could go on and on – whatever you can dream up! | |||
==Race Conditions and Locking== | |||
One of the main reasons that Functional Globals are encouraged in lieu of [[global variable]]s is race conditions. Functional Globals are, by definition, not reentrant so that different calls to the FG will all refer to the same shift registers and hence the same data or resource. This means that when a VI calls a FG, that FG is locked or reserved for use by the calling VI. Other VIs need to wait until it is done before they can call the FG. | |||
Global variables are reentrant. If you try to write a value to the global in several places in your code, updates to the value are unpredictable with respect to time. This means that changes to the global can be subject to race conditions. This is especially true if you read/write to the global where a race condition could occur between the read and the write of the variable. | |||
The downside for Functional Globals is that the locking that takes place can be a bottleneck in the code if many VIs are trying to access the FG at the same time. The majority of applications will not have a problem, but care must be taken if speed is a concern. | |||
It is also important to note that Functional Globals are not a cure-all for race conditions. If actions on the Functional Global are not atomic (i.e. they require more than one call to the FG) then race conditions are still possible. Race conditions are only eliminated if all updates to the data are done inside the FG, in a single call. Here are a couple ways to still allow race conditions with FGs. | |||
*Ask for the data from the FG, change it, and then write it back to the FG. | |||
*Successive modifications, such as {Set data, normalize data, sort data, output max} By the time you sort the data, new data could have been set that hasn’t been normalized. | |||
==Benefits== | |||
#Efficient Memory Usage – Because the VI retains the same data space regardless of it use in the code, only a single call to the memory manager is made. If arrays are stored in the USR, and replace array element primitives used, then the memory space is efficiently used wherever the VI is called. | |||
#Reduces problems with race conditions, as discussed above. | |||
#Error checking can be done on data writes (Global variables don’t allow this). | |||
#High-level code more readable – By moving shift registers and code logic into Functional Globals with readable action lists, top-level code become simpler and cleaner. | |||
#Reuse. Many FGs are generic enough to be used over and over again. | |||
==Concerns== | |||
Terminals get used up quickly, so be sure to design well up front and choose a connector pane with more terminals than you need. | |||
Data in a USR, and hence in a Functional Global, disappears if the owning VI goes out of memory. When a VI runs for the first time, all of its subVIs that are not already in memory are loaded into memory and they belong to that parent VI. If that parent VI is stopped, all of the subVIs that belong to it are also released from memory – even if there are other VIs in memory still referencing those subVIs. The next time one of those subVIs is run, it is loaded into memory again and that calling VI becomes the new parent VI. But if there was anything stored in USRs, that data is now gone. | |||
This behavior is different from a queue reference, for instance, where the queue is only destroyed when ALL VIs referencing it leave memory. It means that if you use Functional Globals across multiple top-level VIs, then you need to have a VI that is run first and contains all of your Functional Globals as subVIs. This ‘First Run’ VI must stay in memory until all other top-level VIs are closed to insure that the Functional Global data will be available. | |||
==See Also== | |||
[[Global variable]]<br /> | |||
[[State machine]] | |||
==External Links== | |||
[http://forums.ni.com/ni/board/message?board.id=170&message.id=240328&query.id=618807#M240328 NI Forums Community Nugget on Action Engines] |
Revision as of 00:30, 9 May 2007
Functional Globals are VIs that allow controlled access to data or resources, often allowing various actions to be performed. Functional Globals (FGs) most often make use of uninitialized shift registers (USRs). USRs are shift registers that do not have a wired input coming into the left side of the loop. A USR stores its data between successive runs of the VI as long as the VI remains in memory.
Alternative Nomenclature
Functional Globals are known by various names among LabVIEW programmers.
- LV2 globals – because LV2 supported uninitialized shift registers
- USR globals – since most FGs make use of uninitialized shift registers
- Action Engines – it has been argued [1] that the term Functional Globals should refer to VIs that just allow read/write access to data. Action Engines would refer to the larger set of VIs that allow some defined action on the data.
The official name for these constructs in NI literature is Functional Globals, and a survey on LAVA indicated that Functional Globals is also the most popular choice.
Structure
The most common form of a Functional Global is a While Loop with a TRUE wired to the stop condition. This forces the loop to terminate after just one execution, meaning the while loop is only used to provide a place to put USRs. A For Loop could also be used with a constant wired to the count terminal. Another method is to use a single-element queue to store the data, though this requires more mouse clicks to implement. The performance of each of these is very close and most likely will vary a little from computer to computer.
In order to specify what action to perform, an enumerated type is often used as an input to the Functional Global. Other methods are possible (Booleans, First Call?, etc.), but an enumerated type provides for a readable, extensible list of actions. Furthermore, saving the enumerated type as a typedef control makes any changes to the list automatically update wherever the Functional Global is used in the code. The ‘action’ input is fed to a case structure which contains the code to implement for each of the defined actions.
For more complex situations, multiple actions can be performed on a single call to the FG. This can be a straightforward state machine, or it can allow for different actions based on the data received or on the existence of resources, etc.
In order to deal with varying input requirements on the different actions, one can create ‘wrappers’ around the Functional Global that allow for different inputs to be required. This also gives the benefit of being able to search your code for certain actions as you can search for a specific VI, but not a specific enumerated type’s value.
Uses
The primary use of Functional Globals is to provide data sharing across an entire project, both horizontally and vertically. Horizontal sharing means multiple top-level VIs running autonomously can all have access to the same data. Vertical sharing means that subVIs at any depth will have access to the data without requiring explicit passing of parameters through VI connector pane terminals. These are common issues in even small-sized programs.
Another common use is to encapsulate some functionality, especially functionality that is linked to persistent data or resources like files or devices. This encapsulation protects the resource and provides an extensible interface for interacting with the data or resource. The encapsulation also allows you to avoid a plethora of shift registers in a single VI. You can offload the shift-register to the Functional Global, and call it when needed, without worrying about wiring the data through all your case structures.
To get a better idea of what can be done with Functional Globals, here is a list of things that can be done:
- Load information from a file into a FG, access it anywhere in your program
- Maintain your system’s configuration parameters in one location.
- Store a control reference (like a tree control) in a FG, and then define an interface to manage that control. For complex controls like trees and multi-column listboxes this can simplify your top-level code.
- Timers are well implemented with FGs. You can keep the start time in the shift register, and then check the elapsed time when you call it again.
- Automatically preallocate an array when the FG is first called, then use “Replace Array Subset” to efficiently manipulate the array.
- Keep an ‘Undo’ list (stack) that allows you to push changes onto a stack, and pop them off when you need to undo the change.
The list could go on and on – whatever you can dream up!
Race Conditions and Locking
One of the main reasons that Functional Globals are encouraged in lieu of global variables is race conditions. Functional Globals are, by definition, not reentrant so that different calls to the FG will all refer to the same shift registers and hence the same data or resource. This means that when a VI calls a FG, that FG is locked or reserved for use by the calling VI. Other VIs need to wait until it is done before they can call the FG.
Global variables are reentrant. If you try to write a value to the global in several places in your code, updates to the value are unpredictable with respect to time. This means that changes to the global can be subject to race conditions. This is especially true if you read/write to the global where a race condition could occur between the read and the write of the variable.
The downside for Functional Globals is that the locking that takes place can be a bottleneck in the code if many VIs are trying to access the FG at the same time. The majority of applications will not have a problem, but care must be taken if speed is a concern.
It is also important to note that Functional Globals are not a cure-all for race conditions. If actions on the Functional Global are not atomic (i.e. they require more than one call to the FG) then race conditions are still possible. Race conditions are only eliminated if all updates to the data are done inside the FG, in a single call. Here are a couple ways to still allow race conditions with FGs.
- Ask for the data from the FG, change it, and then write it back to the FG.
- Successive modifications, such as {Set data, normalize data, sort data, output max} By the time you sort the data, new data could have been set that hasn’t been normalized.
Benefits
- Efficient Memory Usage – Because the VI retains the same data space regardless of it use in the code, only a single call to the memory manager is made. If arrays are stored in the USR, and replace array element primitives used, then the memory space is efficiently used wherever the VI is called.
- Reduces problems with race conditions, as discussed above.
- Error checking can be done on data writes (Global variables don’t allow this).
- High-level code more readable – By moving shift registers and code logic into Functional Globals with readable action lists, top-level code become simpler and cleaner.
- Reuse. Many FGs are generic enough to be used over and over again.
Concerns
Terminals get used up quickly, so be sure to design well up front and choose a connector pane with more terminals than you need.
Data in a USR, and hence in a Functional Global, disappears if the owning VI goes out of memory. When a VI runs for the first time, all of its subVIs that are not already in memory are loaded into memory and they belong to that parent VI. If that parent VI is stopped, all of the subVIs that belong to it are also released from memory – even if there are other VIs in memory still referencing those subVIs. The next time one of those subVIs is run, it is loaded into memory again and that calling VI becomes the new parent VI. But if there was anything stored in USRs, that data is now gone.
This behavior is different from a queue reference, for instance, where the queue is only destroyed when ALL VIs referencing it leave memory. It means that if you use Functional Globals across multiple top-level VIs, then you need to have a VI that is run first and contains all of your Functional Globals as subVIs. This ‘First Run’ VI must stay in memory until all other top-level VIs are closed to insure that the Functional Global data will be available.