In most applications, it is not necessary to use priority levels or an execution system other than the Standard execution system, which automatically handles the multitasking of the VIs. By default, all VIs run in the Standard execution system at Normal priority. In a multithreaded application, a separate thread handles user interface activity, so the VIs are insulated from user interface interaction. In a single-threaded application, the execution system alternates between user interface interaction and execution of the VIs, giving similar results.
In general, the best way to prioritize execution is to use Wait functions to slow down lower priority loops in the application. This is particularly useful in loops for user interface VIs because delays of 100 to 200 ms are barely noticeable to users.
If you use priorities, use them cautiously. If you design higher priority VIs that operate for a while, consider adding waits to those VIs in less time-critical sections of code so they share time with lower priority tasks.
Be careful when manipulating global variables, local variables or other external resources that other tasks change. Use a synchronization technique, such as a functional global variable or a semaphore, to protect access to these resources.
By default, VIs are not reentrant and the execution system will not run multiple calls to the same subVI simultaneously. If you try to call a subVI that is not reentrant from more than one place, one call runs and the other call waits for the first to finish before running. In reentrant execution, calls to multiple instances of a subVI can execute in parallel with distinct and separate data storage. If the subVI is reentrant, the second call can start before the first call finishes running. In a reentrant VI, each instance of the call maintains its own state of information. Then, the execution system runs the same subVI simultaneously from multiple places. Reentrant execution is useful in the following situations:
To make a VI reentrant, select File»VI Properties, select Execution in the VI Properties dialog box, and place a checkmark in the Reentrant execution checkbox.
|Note (FPGA Module) FPGA VIs are reentrant by default.|
When you interactively open a reentrant subVI from the block diagram, LabVIEW opens a clone of the VI instead of the source VI. The title bar of the VI contains (clone) to indicate that it is a clone of the source VI.
|Note Because you cannot perform source control operations on the clone of a source VI, LabVIEW dims the source control operation items in the Tools»Source Control menu of the clone VI.|
You can use the front panels of reentrant VIs the same way you can use the front panels of other VIs. To view the original front panel of a reentrant VI from a clone of the reentrant VI, select View»Browse Relationships»Reentrant Original. Each instance of a reentrant VI has a front panel. You can use the VI Properties dialog box to set a reentrant VI to open the front panel during execution and optionally reclose it after the reentrant VI runs. You also can configure an Event structure case to handle events for front panel objects of a reentrant VI. The front panel of a reentrant VI also can be a subpanel.
You can use the VI Server to programmatically control the front panel controls and indicators on a reentrant VI at run time; however, you cannot edit the controls and indicators at run time. You also can use the VI Server to create a new reentrant instance of the front panel of a reentrant VI at run time. To open a new instance of the front panel of a reentrant VI, use the Open VI Reference function by wiring a strictly typed VI reference to the type specifier input. Use the Run VI method to prepare a VI for reentrant run by wiring 0x08 to the options input.
|Note (FPGA Module) The LabVIEW FPGA Module does not support these VI types. If you use a reentrant subVI in an FPGA VI, each instance of the subVI on the block diagram becomes a separate hardware resource.|
LabVIEW supports two types of reentrant VIs. On the Execution page of the VI Properties dialog box, place a checkmark in the Reentrant execution checkbox to enable the two reentrant VI options. Select the Preallocate clone for each instance option if you want to create a clone VI for each call to the reentrant VI before LabVIEW calls the reentrant VI, or if a clone VI must preserve state information across calls. For example, if a reentrant VI contains an uninitialized shift register or a local variable, property, or method that contains values that must remain for future calls to the clone VI, select the Preallocate clone for each instance option. Also select the Preallocate clone for each instance option if the reentrant VI contains the First Call? function. You also can use this option for VIs that are to run with low jitter on LabVIEW Real-Time.
Select the Share clones between instances option to reduce the memory usage associated with preallocating a large amount of clone VIs. Sharing clone VIs reduces memory usage because LabVIEW does not preallocate a clone for each call to the reentrant VI. When you select the Share clones between instances option, LabVIEW does not create the clone VI until a VI makes a call to the reentrant VI. With this option, LabVIEW creates the clone VIs on demand, potentially introducing jitter into the execution of the VI. LabVIEW does not preserve state information across calls to the reentrant VI. Sharing clone VIs can reduce VI execution speed.
The following table explains the memory usage and execution speed effects to consider when you select a reentrant VI type.
|Reentrant VI Type||Memory Usage||Execution Speed|
|Preallocate clone for each instance||Creates a clone VI for each call to the reentrant VI. Increases memory usage.||Execution speed is constant.|
|Share clones between instances||Only allocates clone VIs for the maximum number of simultaneous calls to the reentrant VI. Decreases memory usage.||Creates clone VIs on demand. Slightly decreases execution speed and speed may vary per call.|
(Windows, ETS, VxWorks) You can configure LabVIEW to either preallocate or share clones of a shared-clone reentrant VI within a Timed Loop or Timed Sequence structure. For example, if you place a shared-clone reentrant VI inside of a Timed Loop or Timed Sequence structure, you can set the structure to preallocate clones for instances of the reentrant VI you call within the structure. Instances of the reentrant VI that you call outside the structure continue to share clones. To set the allocation of a VI within a Timed Loop or a Timed Sequence structure, right-click the structure, select Shared Clone Allocation, and select one of the following:
The following two sections describe examples of reentrant VIs that wait and do not share data.
The following illustration describes a VI, called Snooze, that takes hours and minutes as input and waits until that time arrives. If you want to use this VI simultaneously in more than one location, the VI must be reentrant.
The Get Date/Time In Seconds function reads the current time in seconds, and the Seconds To Date/Time function converts this value to a cluster of time values (year, month, day, hour, minute, second, and day of week). A Bundle function replaces the current hour and minute with values that represent a later time on the same day from the front panel Time To Wake Me cluster control. The Wake-up Time in Seconds function converts the adjusted record back to seconds, and multiplies the difference between the current time in seconds and the future time by 1,000 to obtain milliseconds. The result passes to a Wait (ms) function.
The Lunch VI and the Break VI use Snooze as a subVI. The Lunch VI, whose front panel and block diagram are shown in the following illustration, waits until noon and displays a front panel to remind the operator to go to lunch. The Break VI displays a front panel to remind the operator to go on break at 10:00 a.m. The Break VI is identical to the Lunch VI, except the display messages are different.
For the Lunch VI and the Break VI to run in parallel, the Snooze VI must be reentrant. Otherwise, if you start the Lunch VI first, the Break VI waits until the Snooze VI wakes up at noon, which is two hours late.
If you make multiple calls to a subVI that stores data, you must use reentrant execution. For example, you create a subVI, ExpAvg, that calculates a running exponential average of four data points.
Another VI uses the ExpAvg subVI to calculate the running average of two data acquisition channels. The VI monitors the voltages at two points in a process and displays the exponential running average on a strip chart. The block diagram of the VI contains two instances of the ExpAvg subVI. The calls alternate — one for Channel 0, and one for Channel 1. Assume Channel 0 runs first. If the ExpAvg subVI is not reentrant, the call for Channel 1 uses the average computed by the call for Channel 0, and the call for Channel 0 uses the average computed by the call for Channel 1. By making the ExpAvg subVI reentrant, each call can run independently without sharing the data.
To allow debugging on a reentrant VI, select File»VI Properties to display the VI Properties dialog box, select Execution from the pull-down menu, and place a checkmark in the Allow debugging checkbox.
When you open a reentrant subVI from the block diagram, LabVIEW opens a clone of the VI instead of the source VI. The title bar of the VI contains (clone) to indicate that it is a clone of the source VI. You cannot edit the clone VI. You can use the block diagram of the copy of the reentrant VI for debugging purposes; however, you cannot edit the block diagram instance. Within the block diagram, you can set breakpoints, use probes, enable execution highlighting, and single-step through execution. When you debug a reentrant VI with the Share clones between instances option selected on the Execution page of the VI Properties dialog box, do not set breakpoints, use probes, or enable execution highlighting in the clone VI. The clone VI does not maintain the debugging settings across calls. If you set the debugging settings in the original reentrant VI, the clone VIs maintain the original debugging settings.
If you need to edit a reentrant VI, you must open the original reentrant VI instead of the clone. You can open the reentrant VI from the clone by selecting Operate»Change to Edit Mode. LabVIEW opens the reentrant VI in edit mode. Alternatively, you also can select View»Browse Relationships»Reentrant Original. After LabVIEW opens the reentrant VI, select Operate»Change to Edit Mode to make the VI editable.
|Note When you debug applications and shared libraries, you cannot debug reentrant panels that an Open VI Reference function creates. You also cannot debug reentrant panels that are entry points to LabVIEW-built shared libraries.|
Because the execution system can run several tasks in parallel, you must make sure global and local variables and resources are accessed in the proper order.
You can prevent race conditions in one of several ways. The simplest way is to have only one place in the entire application through which a global variable is changed.
In a single-threaded application, you can use a Subroutine priority VI to read from or write to a global variable without causing a race condition because a Subroutine priority VI does not share the execution thread with any other VIs. In a multithreaded application, the Subroutine priority level does not guarantee exclusive access to a global variable because another VI running in another thread can access the global variable at the same time.
Another way to avoid race conditions associated with global variables is to use 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. The following illustration shows a functional global variable that implements a simple count global variable. The actions in this example are initialize, read, increment, and decrement.
Every time you call the VI, the block diagram in the loop runs exactly once. Depending on the action parameter, the case inside the loop initializes, does not change, incrementally increases, or incrementally decreases the value of the shift register.
Although you can use functional global variables to implement simple global variables, as shown in the previous example, they are especially useful when implementing more complex data structures, such as a stack or a queue buffer. You also can use functional global variables to protect access to global resources, such as files, instruments, and data acquisition devices, that you cannot represent with a global variable.
You can solve most synchronization problems with functional global variables, because the functional global VI ensures that only one caller at a time changes the data it contains. One disadvantage of functional global variables is that when you want to change the way you modify the resource they hold, you must change the functional global VI block diagram and add a new action. In some applications, where the use of global resources changes frequently, these changes might be inconvenient. In such cases, design the application to use a semaphore to protect access to the global resource.
A semaphore, also known as a mutex, is an object you can use to protect access to shared resources. The code where the shared resources are accessed is called a critical section. A semaphore protects access to a critical section by allowing only a certain number of tasks to acquire access to the semaphore at the same time. In general, you want only one task at a time to have access to a critical section protected by a common semaphore. However, it is possible for semaphores to permit more than one task (up to a predefined limit) access to a critical section.
To specify the number of tasks that can access a critical section protected by a common semaphore, wire the size input of the Obtain Semaphore Reference VI. This VI obtains a reference to an existing semaphore or creates a new semaphore, and the size input specifies how many tasks can acquire the semaphore at the same time. Use the Acquire Semaphore VI to acquire access to a semaphore. If the size of the semaphore you created with the Obtain Semaphore Reference VI is 1, only one task at a time can acquire access to the semaphore. Therefore, you must use the Release Semaphore VI to release access to the semaphore before another task can acquire access to the semaphore. Use the Release Semaphore Reference VI to release a reference to a semaphore.
|Note A semaphore remains in memory as long as a top-level VI that is not idle owns a reference to the semaphore. If the top-level VI becomes idle, LabVIEW releases all semaphore references the VI owns, including references that exist in subVIs of the top-level VI. If LabVIEW releases the last reference to a named semaphore, LabVIEW destroys the semaphore. Because you can obtain only one reference to an unnamed semaphore, LabVIEW destroys an unnamed semaphore when the top-level VI becomes idle. To use a semaphore across multiple top-level VIs, name the semaphore and call the Obtain Semaphore Reference VI from each top-level VI so that each VI obtains its own unique reference to the semaphore.|
Refer to the Semaphore with SubVIs VI in the labview\examples\general\semaphore.llb for an example of using the Obtain Semaphore Reference VI and the Release Semaphore Reference VI.
The following illustration shows how you can use a semaphore to protect the critical sections. The semaphore was created by entering 1 in the size input of the Obtain Semaphore Reference VI.
Each block diagram that wants to run a critical section must first call the Acquire Semaphore VI. If the semaphore is busy (its size is 0), the VI waits until the semaphore becomes available. When the Acquire Semaphore VI returns false for timed out, indicating that it acquired the semaphore, the block diagram starts executing the false case. When the block diagram finishes with its critical section (Sequence frame), the Release Semaphore VI releases the semaphore, permitting another waiting block diagram to resume execution.