Related Resources |
---|
This article is part of a series. To see more design patterns, please visit Applying Common Object-Oriented (OO) Design Patterns to LabVIEW |
This is part of a series of recommendations on how to implement some of the famous Gang of Four OOP design patterns using LVOOP. It represents an open discussion with the NI Community, so please feel free to provide comments and suggestions if you have any!
Guarantee that a given class has only a single instance in memory, that no second instance can ever be created, and that all method calls refer to this single instance.
When you create a class, sometimes it is advantageous to guarantee that a program always refers to the same global instance of the class. Perhaps the class represents a database. It would be unfortunate if some section of code accidentally instantiated its own database and thus failed to update the global database. Creating a global variable means all VIs can access the data, but it does not mean that all VIs will access the global variable. This pattern describes a class that is designed to ensure all its clients access the same data.
To implement a Singleton, two general requirements must be met:
In LabVIEW, there are multiple technologies available for achieving these goals. Two implementations are provided here as examples. The first employs a single-element queue (SEQ) to manage the instance and a functional global variable (FGV) to provide universal access. The second replaces the SEQ with a Data Value Reference (DVR) and provides access with a Notifier.
An example of this implementation shipped with LV8.2. That version turned out to be a bad idea. A better design has since been found, so please see the revised version that shipped in LabVIEW 8.6: <labview>\examples\lvoop\SingletonPattern\Singleton.lvproj
Checkin.vi and Checkout.vi use an SEQ to guarantee only one copy in memory at a time. While the element is checked out to be modified by some routine, no other operation can proceed, thus guaranteeing serial access to the data. Dequeuing the object provides the client exclusive access to its data (via public methods), until it chooses to re-enqueue the object by checking it in. In this implementation, the queue acts as a reference to the object, which can be accessed from anywhere in the application through "Checkout" and "Checkin". The reference is persisted in an FGV, which provides a copy to any client that calls the "Get Queue" method. If the object has not yet been created (on the first call), it creates an instance and adds it to the SEQ; every call thereafter, a copy of the reference to that first instance is used.
This implementation also provides a design for guaranteeing a single instance of Data.lvclass. We achieve this by putting Data.lvclass into an owning library, Singleton.lvlib, and making the class private. Now no VI outside the class can ever drop the class as a control, constant or indicator, so we guarantee that all operations are limited to this library. Callers can use Singleton.lvlib:Checkout.vi to get the current value of the data, modify it using any of the operations defined by Singleton.lvlib and then set the new value with Singleton.lvlib:Checkin.vi. The public functions, which would normally be on the class itself, have been moved to the library so their functionality is exposed without allowing the class itself to be dropped.
The attached example provides this implementation. It is derived from the Extensible Session Framework (ESF).
In this design, the client requests access to the singleton by providing a default instance -- a newly dropped object -- of the singleton to its "Get Instance" method. The method then outputs a reference to the persistent instance, which is used by the client in lieu of its dropped instance. Inside the method, it can be seen that the singleton object is held in a notifier instead of an FGV. This provides persistent global access to the object using a native LabVIEW feature. A reference is still needed to give access to methods from every client in the application, so a DVR to the object is held in the class private data. (This is normally forbidden, but placing the DVR in a variant first lets you circumvent the compiler.) When the object is popped from its notifier, the DVR is taken out of the object and passed out to the client.
A DVR is used instead of an SEQ here. The two options are largely interchangeable, but the DVR allows use of the In Place Element Structure when referencing and dereferencing the object. In LabVIEW 2010, DVRs also propagate type to object property nodes, so properties can be accessed directly via the DVR. DVRs are not available prior to LabVIEW 2009.
Note that while the "Get Instance" method is required to gain access to the share instance of Singleton.lvclass, it is not required that the client use it to instantiate the class. The client could just drop an object of the class type and use that object. This lack of protection against multiple instances violates one of the requirements for a Singleton design, but it can be solved in the same way as the previous example: place the class inside a library and mark it private, then place public methods outside the class (but still in the library). That protection mechanism is not shown here, because it may be deemed unnecessary for many applications and libraries.
[David Staab] The Singleton is meant to act as a global state/data repository, so here's an important note: in both implementations, the reference -- and the object it points to -- is maintained by the client that first created it. It is not truly self-persistent. This means that when the client process leaves memory, the reference, SEQ or DVR, will become invalid. So when working with a Singleton class in LabVIEW, it is very important to maintain awareness of client lifetimes.
Although the DVR+Notifier design takes a default instance of the class as an argument to the "Get Instance" method, it only uses this object to provide a unique string name to the Notifier so the same Notifier can be accessed every time. It is interesting to note here that the Notifier engine built into LabVIEW is actually acting as a registry of Singletons. The design can be altered to take a string name as an argument to "Get Instance", in which case any of a hierarchy of Singleton classes could be returned by the function. (This is what ESF does.)
There are lots of situations that call for multiple instances of a class (optionally with a fixed limit to their quantity), but the needs still exist to initialize each of them uniquely and to provide references to each of them globally. Consider using ESF in these situations, instead of developing your own customized Singleton.
[Stephen Mercer] Please do not use the version of this pattern that shipped with LV8.2. Although the example that shipped does show the basics of this pattern, many of its details turned out to be bad ideas in practice. The example was revised in LV8.6, drawing on the experiences of multiple users.
Personally, I have no use for this pattern. In LabVIEW, I find it kind of silly, as it is the sort of pattern that develops when you’re already breaking dataflow. But I include it because it seems many people desire to see how it could be done in our language.
The only hole in the above solution that I can find is VI Server. VI Server could get a reference to the Front Panel controls of Singleton’s member VIs and use the Value property to effectively manipulate a different instance of the class than the one in the queue. Would using Subroutine Priority prevent VI Server access? I think so, but that seems like a pretty odd way to fix these VIs.
How the race condition problem is address with this implementation? There is only one object of the class in memory but the handle could be owned by many processes. For the SEQ implementation the dequeue function guarantee that the race condition won't happen. But with nofitier how this problem is avoided? Thanks!
Race conditions are circumvented by adding a checkout/checkin protocol to the Singleton. As you noted, the SEQ example that ships with LV has this, while the attached Notifier example doesn't. It's straightforward to add such methods to the class, though. I've done so on a file I/O component I'm working on at the moment.
Most recent version of this pattern can be found installed under [LabVIEW directory]\examples\lvoop\SingletonPattern
Hey all,
I'm looking for the example SingletonPattern but I can't find in LabVIEW 2013 sp1 version,
\National Instruments\LabVIEW 2013\examples\Object-Oriented Programming
There is no lvoop folder at all and even I check the [Object-Oriented Programming] folder, the SingletonPattern is not existed. Anyone knows why ?
The examples were moved around in LV 2013... the OO examples are now in
The Singleton Pattern was removed. I do not know why.
(Although I suspect the answer is that the Singleton Pattern is thought by many people to be best implemented in LabVIEW *without* LabVIEW classes. Use an LV2-style global Action Engine instead. There are pros/cons to that solution, I admit, but I can see it being a reason that the examples team would have eliminated this example.)
I would highly suggest using the Singleton pattern that comes with the NI GOOP Development Suite (http://sine.ni.com/nips/cds/view/p/lang/nl/nid/209038). It combines the pro's of a LV2-style global and a 'normal' class. You get to have different methods for your actions (instead of having the enum with a one-size-fits all VI) and lose the class wire!
AristosQueue wrote:
(Although I suspect the answer is that the Singleton Pattern is thought by many people to be best implemented in LabVIEW *without* LabVIEW classes. Use an LV2-style global Action Engine instead. There are pros/cons to that solution, I admit, but I can see it being a reason that the examples team would have eliminated this example.)
It looks like eliminating the SEQ Singleton example from LV13.0 is the outcome of my Service Request #734505 filed in February 2012. Although I had not been aware Example Team pulled the plug on it until seeing this post …
I was working on a “Singleton Design Pattern” presentation for a Bay Area LabVIEW User Group Meeting back then, and carefully reviewed all available Singleton LabVIEW implementations. To my surprise, SEQ implementation provided enough opportunities for abuse by the caller – resulting in code hang-ups and returning different values on two subsequent Get Data calls. The latter does not allow calling it a Singleton in the first place …
For details, please see my PowerPoint presentation and \LV2011_Singleton_Example\Singleton (Midified by DS).lvproj available for download at https://decibel.ni.com/content/docs/DOC-20865.
I do use Singletons for implementing Registries, Global Logs and Global Error Handlers. The best class-based implementation I know was first introduced by AQ @ NI Week 2010 (see \AQ_Singleton_Example from above zip file). I am using it in all my Singleton designs since then.
Reference the DVR Method.
BLUF: Be careful with this method if you call your Singleton's from different namespaces.
You're basically flattening the class constant to a string and feeding that as the name of the notifier. Clever and usually fool-proof.
This can be problematic. Embedded in the constant is the classes name, and this includes the library name from which it lives. I had all sorts of fits calling this code from both a packed library and NI TestStand. When the methods get called from different locations, the library names differ in the class constants and now we have MULTIPLE SINGLETONs. Which is the thing we are trying to prevent. One solution was to put everything in one Packed Library and make sure everyone calls it from there. Works great. Just a bit of discipline.
Now I have fairly large packed libraries (~1500 VIs or so). TestStand is incredibly slow loading them. I really would like to break up the band.
My thought is to break up these packed libraries, but doing so will return me to my name-spacing purgatory. To get around this, my thought was to abandon the flattening the class constant to a string for the notifier name, and provide a unique (heavily salted) name for the notifier myself. In fact, I'll just create a subVI with this heavily salted name as a constant that returns as a string indicator -- that I can reuse everywhere w/o fear of typos.
Can anyone think of a downside of this approach?