Suggestions for Using Execution Systems and Priorities
Following is a summary of some general suggestions about using the execution system options described in this document.
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. Even 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.
Priority use in single-threaded and multithreaded applications gives fairly similar results with the same VIs. However, subtle timing differences might exist. If you distribute VIs to customers who run a different operating system, consider testing the applications under those conditions. Make a multithreaded application behave as a single-threaded application by removing the checkmark from the Run with multiple threads on the Performance and Disk page of the Options dialog box.
Simultaneously Calling SubVIs from Multiple Places
Under normal circumstances, the execution system cannot 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:
- When a VI waits for a specified length of time or until a timeout occurs
- When a VI contains data that should not be shared among multiple instances of the same VI
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.
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 can use the front panels of reentrant VIs the same way you can use the front panels of other VIs. To view the 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 copy of the front panel of a reentrant VI at run time. To copy the front panel of a reentrant VI, use the Open VI Reference function to open a VI Server reference. Wire a strictly typed VI reference to the type specifier input or wire open for reentrant run to the option input. When you open a reference, LabVIEW creates a copy of the VI. You also can use the VI Server or the VI Properties dialog box to open the front panel of the reentrant VI.
Examples of Reentrant Execution
The following two sections describe examples of reentrant VIs that wait and do not share data.
Using a VI that Waits
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 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.
Using a Storage VI Not Meant to Share Its Data
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.
Synchronizing Access to Global and Local Variables and External Resources
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.
Preventing Race Conditions
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.
Functional Global Variables
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.
Semaphores
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 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. In general, you want only one task at a time to have access to a critical section protected by a common semaphore. It is possible for semaphores to permit more than one task (up to a predefined limit) access to a critical section.
A semaphore remains in memory as long as the top-level VI with which it is associated is not idle. If the top-level VI becomes idle, LabVIEW clears the semaphore from memory. To prevent this, name the semaphore. LabVIEW clears a named semaphore from memory only when the top-level VI with which it is associated is closed.
Use the Create Semaphore VI to create a new semaphore. Use the Acquire Semaphore VI to acquire access to a semaphore. Use the Release Semaphore VI to release access to a semaphore. Use the Destroy Semaphore VI to destroy the specified semaphore.
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 Create Semaphore 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 Acquire Semaphore VI releases the semaphore, permitting another waiting block diagram to resume execution.
posted on 2006-01-12 12:18 LabVIEW开发者 阅读(503) 评论(0) 编辑 收藏 举报