LabVIEW Development Best Practices Documents

cancel
Showing results for 
Search instead for 
Did you mean: 

Singleton Pattern

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!

Intent

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.

Motivation

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.

Implementation

To implement a Singleton, two general requirements must be met:

  • There can only be one instance of the class
  • It must be universally accessible to clients by a well-known means

 

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.

SEQ and FGV Implementation

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.

DVR and Notifier Implementation

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.

Editorial Comments

[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.

David Staab, CLA
Staff Systems Engineer
National Instruments
Comments
tom_z
Member
Member
on

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!

David S.
NI Employee (retired)
on

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.

David Staab, CLA
Staff Systems Engineer
National Instruments
dpnsw
Member
Member
on

Most recent version of this pattern can be found installed under [LabVIEW directory]\examples\lvoop\SingletonPattern

tss1433
NI Employee (retired)
on

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 ?

Regards,
Sean
AristosQueue (NI)
NI Employee (retired)
on

The examples were moved around in LV 2013... the OO examples are now in

  • <LabVIEW>\examples\Object-Oriented Programming\

The Singleton Pattern was removed. I do not know why.

AristosQueue (NI)
NI Employee (retired)
on

(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.)

JeffreyH
Member Member
Member
on

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!

Dmitry
Active Participant Active Participant
Active Participant
on

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 …

SEQ Singleton Implementation Conundrum.PNG

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.