From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

Dr. Damien's Development - Single-Element Queue Reference Objects

A reference object is an object referred to by reference rather than by value.  LabVIEW, being a data flow language, greatly prefers the by-value approach.  Creating a reference object in LabVIEW is not difficult, but it is non-obvious and there are several pitfalls.  You can get the full story on reference objects here.  Reference objects are typically used to store state information in large applications.  In smaller applications, local shift registers are usually a better choice.

 

The two best reference object implementations in LabVIEW are the LabVIEW 2 Global (aka shift register global, functional global, action engine) and the single-element queue.  Ben did a great job writing up the action engine approach in this tutorial.  This is a similar, though less extensive, tutorial on the single-element queue approach.

 

The attached ZIP file contains all the code referenced in this tutorial.  It is LabVIEW 7.1.1, so most folks should be able to open it.  It can be ported back to about LabVIEW 5.1? (when queues became polymorphic), if needed.

 

To use the attached code, unzip it and open rc_Data.ctl. Change the data to your particular data set and save it.  Now open rc_DataRef.ctl.  Finally, open everything else.  You are now ready to copy the template to a new name and location.  Copy rc_Data.ctl to its final location and name (libraries or objects are recommended if you are using LabVIEW 8.0 or above).  Then copy rc_DataRef.ctl followed by the rest of the code.  You may want to take the opportunity to change rc_Set.vi and rc_Get.vi to use your data and rename them to something reasonable.  When you modify the new rc_Data.ctl in the future, it is recommended you close the other VIs first due to the double type propagation which takes place through the queue reference.  Earlier versions of LabVIEW (8.0 in particular) do not handle this propagation very well.  When you reopen them with the new typedef, all will be well.  If you do have them open, you can either explicitly apply the changes (File->Apply Changes) or close and reopen.  You may need to apply changes for both controls if you choose that route, depending on your version of LabVIEW.

 

Create Set/Get VIs for any new data you place in the data control.  Resist the urge to directly access the queue in your application code.  This makes it very difficult to maintain your code, since you now have to change each instance of the queue access instead of a single VI if your data changes or needs to be processed (this happens a fair amount, especially with older code).  Also create any operate VIs needed to process your data.

 

Now rewrite the create and destroy VIs to include any initialization and cleanup code needed. When the create VI exits, the data should be in a good state so it can be used immediately.  Similarly, when the destroy VI exits, the object should be completely cleaned up.

 

How do you actually use it?  Start with the create VI.  This creates a queue with a single element and stuffs the default data into that element.  This is similar to a constructor in C++.  To set/get/operate on your data, call the appropriate get/set/operate VIs that you have written, using the queue reference as your reference.  When you finish, call the destroy VI.  This is similar to a destructor in C++.

 

When you create a new Get/Set VI, it is usually easier to copy an existing one and modify it than to start a new one from scratch.

 

Why would you use single-element queues instead of action engines? First, the single-element queue approach outperforms action engines for large data sets since it is easier to operate in-place (the in-place element helps, as well - use it).  The single-element queue approach is more scalable, since each get/set/action is a separate VI.  An action engine is limited by its connector pane.  It can only get/set so many data types.  A cluster with ten different data types is a fairly modest object, but would push the limits of an action engine.  The final answer, however, is that this depends on your personal programming style.  You should do what you are more comfortable with.

 

Finally, what if you want a true reference object with inheritance?  You can combine the single-element queue with the LabVIEW object (which is by value) to accomplish this.  See this musical instrument tuner demo for an example. Going through the LabVIEW object does slow the queue data access down a small amount, but not much.

 

Have fun!

Message 1 of 14
(13,052 Views)

This solution is a singleton design pattern.This pattern was first decribed in the book Design Patterns by Gamma, Helm, Johnson and Vlissides.

 

An LVOOP solution was presented by Aristos Queue on the LAVA forum.

 

DFGray thanks for showing how to implement that in non-LVOOP.

Waldemar

Using 7.1.1, 8.5.1, 8.6.1, 2009 on XP and RT
Don't forget to give Kudos to good answers and/or questions
Message 2 of 14
(12,973 Views)

A couple questions on this architecture:

 

1) Why single element Queue instead of the Notifier?

 

2) A developer would have to be aware of any potential race conditions with Get and Set happening in non-data flow controlled code, i.e. maybe in dynamically called VIs that are utlizing the named Queue.  The one plus for the LV 2 global (Functional Global, etc. using Shift Registers) is that it is blocking.

 

If I am wrong about this let me know.

 

3) Why not use Preview instead of removing the element from the Queue and stuffing it back into the Queue in the Get operation?

Ryan Vallieu CLA, CLED
Senior Systems Analyst II
NASA Ames Research Center
Message 3 of 14
(12,400 Views)

I believe the answer to both first questions (and possibly the third) is essentially the same - the whole point about the single element queue is that you start each operation by dequeuing the element. This locks the queue and forces every other VI acting on the queue to wait until you push the element back into the queue. This guarantees the locking you wanted and is the reason you can't use a notifier.

 

You should note that while you can't have race conditions if you do this in all the VIs (since you do lock-modify-unlock), you can have deadlocks (e.g. if you call one VI which wants the object inside another VI, since the subVI will have to wait for the queue which will never be released).

 

If you will look at the end of the thread Damien linked to (Ben's nugget), you will see an example I posted which demonstrates using this technique as well.


___________________
Try to take over the world!
Message 4 of 14
(12,390 Views)
If I had thought about it a little harder I would have seen that removing the element from the Queue does in fact protect the operations from a race condition since this is a single element Queue.  Thanks.
Ryan Vallieu CLA, CLED
Senior Systems Analyst II
NASA Ames Research Center
0 Kudos
Message 5 of 14
(12,388 Views)

New designs should probably use the Data Value Reference (DVR) instead of a single-element queue.  The semantics are virtually identical, but the DVR is a tad faster.  The only reason to use a single-element queue is to allow naming of the reference.  You can acquire a queue by name.  This is not true for a DVR.

Message 6 of 14
(8,578 Views)

@DFGray wrote:

[...] the DVR is a tad faster.  [...]


Do you have a benchmark?

 

Richard






0 Kudos
Message 7 of 14
(8,564 Views)

Yes.  I have attached it.  I remembered incorrectly.  The DVR is actually about twice as fast as a single-element queue - on par with, but not quite as good as, a global.  I checked in LabVIEW 2009 and 2011 with similar results.  Note that the tests should be run outside a project.  The VIs to run are niDB Array Read Test.vi and niDB Speed Test.vi.

 

Also note that the array tests should be rewritten.  The most efficient way to get a subelement of an array is to use the In Place Element structure to extract it.  This should be done in a subVI or the repository, not on the main diagram.  Doing this could result in some major rearrangement of what method comes out best.  The Action Engine, in particular, would greatly benefit from this.

 

Note that this is an updated version of the tests I posted here.  The DVR was added and GOOP was removed.

Message 8 of 14
(8,530 Views)

Very nice! Thanks.

 

During consideration of these methods, we should note that the AE has the advantage of being "wireless".

 


DFGray wrote:

[...]

 Note that the tests should be run outside a project.  The VIs to run are niDB Array Read Test.vi and niDB Speed Test.vi.

[...]


 

But, since 'Speed Test' uses Shared Variables.... don't we have to launch from Proj?

 

 

Richard






0 Kudos
Message 9 of 14
(8,509 Views)

Yes.  The shared variable tests need the project to function.  If you run outside the project, they will not work.  The other tests will run well in either environment, and better outside the project.

 

You should also close all block diagrams, since rendering them can slow things down a bit.  Shut off virus scanning, e-mail programs, screensaver, etc.  Run several times and take the lowest number (which represents the fewest interruptions by the OS).

0 Kudos
Message 10 of 14
(8,495 Views)