Jump to content

Singleton Pattern redux


Recommended Posts

So I was re-examining some design pattern postings and ran across this statement again (and other similar ones) about the Singleton example (based on SEQ and FGV) from LabVIEW 8.2:

  • An example of this implementation shipped with LV8.2. That version turned out to be a bad idea.

Haivng never looked at this example originally, I loaded up 8.2 to see what the fuss was about. I also searched for an explanantion or discussion of what exactly was bad or what the issues were, but couldn't find anything. So without straining my brain too hard this late at night, what exactly were the issues that people found?

And comparing the shipping Singleton example from 8.6 -> 2010 to what AQ posted here, I prefer the DVR version as I think most people would. Having said that, if one was stuck with LabVIEW 8.6 or earlier (no DVR) and needed a singleton pattern, why would you use the shipping example vs. doing the same thing the DVR implementation does, but with an SEQ:

post-415-0-40751000-1298442872_thumb.png

You get many of the benefits AQ mentioned in the post referenced above: No wrapping library, no replication of VI's,...

Am I missing something? I would think this would have been the shipping example, but is this conversion from DVR to SEQ so simple I'm overlooking something. I don't think so, but intersted in your opinions.

-Scott

Link to comment

Isn't the LV 8.6 one prone to a race condition? (Imagine two VIs trying those queue operations at the same time; if the timing is unfortunate, the two would interfere).

I believe that the in-place element structure (IPES?) has an internal semaphore mechanism to prevent such race conditions.

Link to comment

Isn't the LV 8.6 one prone to a race condition? (Imagine two VIs trying those queue operations at the same time; if the timing is unfortunate, the two would interfere).

I believe that the in-place element structure (IPES?) has an internal semaphore mechanism to prevent such race conditions.

It's not clear in the images, but I mentioned in the post that this is based on an SEQ (single element queue). The process of dequeueing the class will cause any other parallel operation to wait at it's dequeue until the class is enqueued again. This proivides the locking and serialization mechanism. The IPE handles this imiplicilty for DVR, while the class developer has to be cognizant to do the proper dequeue/enqueue operations for the SEQ implementation.

Link to comment

It's not clear in the images, but I mentioned in the post that this is based on an SEQ (single element queue). The process of dequeueing the class will cause any other parallel operation to wait at it's dequeue until the class is enqueued again. This proivides the locking and serialization mechanism. The IPE handles this imiplicilty for DVR, while the class developer has to be cognizant to do the proper dequeue/enqueue operations for the SEQ implementation.

Ah, of course, you're right. Thanks a lot for the correction!

In that case, I'm also very curious...

Link to comment

DVRs and SEQs behave identically (except DVRs have a clearer locking scope because of the IPE structure).

Yes, I agree. That is what prompted the latter part of my post. Why wouldn't this be the example of a Singleton that ships with LabVIEW (SEQ for 8.6; DVR for 2009+). Currently the example is a class wrapped in a library that uses a checkout/checkin mechanism to provide the locking. The checkout/in is still based on an SEQ, even in 2010. AQ had stated that he had wanted to see how DVR's were accepted before proposing his DVR method become the shipping example for 2009. OK, I'll give benefit of the doubt on that one. But why wasn't the example updated for 2010.

I'm assuming that in 8.2 the example looked different than it does in 8.6.

There are diferences between the 8.2 version and current version, but both are based on SEQ+FGV. The current shipping example is the same for 8.6 through 2010.

Actually the 8.2 version is somewhat closer to AQ's DVR implementation and the alternative for 8.6 using an SEQ that I mentioned above. The major difference is that the class is wrapped in a library and the public API is in the library and it wraps the class functions. The class is private to the library. There are still checkout/checkin VI's but they aren't actually used in the example or the other API functions. It's almost like they were an afterthought or that you were expected to use them even though the othe API examples don't. The API examples dont have class inputs or use the checkout/in; they just get the SEQ from the FGV and dequeue/enqueue the class. It's an inconsistent message.

The 8.6 and up version is pushing you towards explicit checkout/in. The class is still wraped in a library and the public API is part of the library and wraps the private class functions. But the public API has class inputs so you have to do a checkout to do any operation and then checkin. The public API does not checkout/dequeue or enqueue/checkin internally like the 8.2 version or the DVR version. The user has to do that and then call the API functions.

That's what is prompting these questions. The fundamental mechanism is the same whether it is SEQ+FGV or DVR+FGV. The fact that the original 8.2 example is a little closer to the DVR+FGV version that AQ posted makes me question what issues were encountered with it. Is it the wrapping library (still used in the "better" 8.6+ versions)? Is it the inconsistency? Is it the fact that the public Get method uses Preview queue vs dequeue? Something else entirely?

I'm attaching the original 8.2 version for those that don't have 8.2 and want to see it. I'm also attaching an 8.6 version that duplicates the DVR implementation but using an SEQ. The DVR version by AQ is linked above and I assume everyone can get the current shipping example in 8.6 or higher.

-Scott

SingletonPattern82.zip

SingletonData 86.zip

Link to comment

Yes, I agree. That is what prompted the latter part of my post. Why wouldn't this be the example of a Singleton that ships with LabVIEW (SEQ for 8.6; DVR for 2009+). Currently the example is a class wrapped in a library that uses a checkout/checkin mechanism to provide the locking. The checkout/in is still based on an SEQ, even in 2010. AQ had stated that he had wanted to see how DVR's were accepted before proposing his DVR method become the shipping example for 2009. OK, I'll give benefit of the doubt on that one. But why wasn't the example updated for 2010.

There are diferences between the 8.2 version and current version, but both are based on SEQ+FGV. The current shipping example is the same for 8.6 through 2010.

Actually the 8.2 version is somewhat closer to AQ's DVR implementation and the alternative for 8.6 using an SEQ that I mentioned above. The major difference is that the class is wrapped in a library and the public API is in the library and it wraps the class functions. The class is private to the library. There are still checkout/checkin VI's but they aren't actually used in the example or the other API functions. It's almost like they were an afterthought or that you were expected to use them even though the othe API examples don't. The API examples dont have class inputs or use the checkout/in; they just get the SEQ from the FGV and dequeue/enqueue the class. It's an inconsistent message.

The 8.6 and up version is pushing you towards explicit checkout/in. The class is still wraped in a library and the public API is part of the library and wraps the private class functions. But the public API has class inputs so you have to do a checkout to do any operation and then checkin. The public API does not checkout/dequeue or enqueue/checkin internally like the 8.2 version or the DVR version. The user has to do that and then call the API functions.

That's what is prompting these questions. The fundamental mechanism is the same whether it is SEQ+FGV or DVR+FGV. The fact that the original 8.2 example is a little closer to the DVR+FGV version that AQ posted makes me question what issues were encountered with it. Is it the wrapping library (still used in the "better" 8.6+ versions)? Is it the inconsistency? Is it the fact that the public Get method uses Preview queue vs dequeue? Something else entirely?

I'm attaching the original 8.2 version for those that don't have 8.2 and want to see it. I'm also attaching an 8.6 version that duplicates the DVR implementation but using an SEQ. The DVR version by AQ is linked above and I assume everyone can get the current shipping example in 8.6 or higher.

-Scott

DVRs and SEQs do not function identically.

DVRs (when used with LV classes) require you to explicitly verify the data type when you write the in place element structure. This is done using the preserve runtime class VI. This means you cannot change the data type of class (from one child to another or child to parent.)

You can change the data type of a class in a SEQ, as long as the class is a child of the original data type.

~Jon

Link to comment

DVRs and SEQs do not function identically.

DVRs (when used with LV classes) require you to explicitly verify the data type when you write the in place element structure. This is done using the preserve runtime class VI. This means you cannot change the data type of class (from one child to another or child to parent.)

You can change the data type of a class in a SEQ, as long as the class is a child of the original data type.

~Jon

I guess Yair and I could have been a bit more explicit when stating DVR's and SEQ's function identically. Of course there are differences, but we were referring specifically to the locking capability, not type handling. From the locking perspective, they function very much the same. Though as Yair points out, the scope of the lock is clearer with DVRs becasue of the IPE structure.

Try saving some basic DVR code to 8.6. What do you get? An implementation based on SEQ.

Link to comment

[8.2 vs 8.6 SEQ FGV] I am pretty sure AQ has posted this already on LAVA. From what I recall he said there was an issue with the 8.2 implementation that prompted the fix for 8.6. And that he did not recommend using the 8.2 version. I guess he is the best person to answer.

FWIW the 2009 DVR implementation rocks. I really like the simplicity of it.

Link to comment

[8.2 vs 8.6 SEQ FGV] I am pretty sure AQ has posted this already on LAVA. From what I recall he said there was an issue with the 8.2 implementation that prompted the fix for 8.6. And that he did not recommend using the 8.2 version. I guess he is the best person to answer.

Yea, that's what I had hoped, but I couldn't find anything here or on the NI forums that specifically discusses the 8.2 implementation. Maybe I'm not searching right, but all I get is that the original 8.2 example is "flawed" or a "bad idea", but no discussion of why.

FWIW the 2009 DVR implementation rocks. I really like the simplicity of it.

Right! That's why I think the same implementation in 8.6 but substituting an SEQ for the DVR would be better than the shipping example, but I guess no one really cares about it any more. :rolleyes:

It's more of a thought experiment and wondering "Why?" at this point.

There are more interesting discussions of OOP going on than what to do with the singleton pattern in 8.2 or 8.6.

Link to comment

In my opinion, a true singleton is not possible in LabVIEW. You simply can not enforce that only a single value of a given type ever exists.

The only way to ensure you have control over the number of instances of a type is to exert control over creation of that type. In other languages, this is done via a private constructor for objects. This is not a problem because you can still pass pointers or references to a private type through function calls, allowing the functions to operate in place on another value.

In LabVIEW we face a fundamental problem: constructors simply do not exist. You can get around the lack of constructors by placing your type in a wrapping library and making the type itself private, then creating a public API for accessing that type. However then another LabVIEW fundamental comes around to smack you in the face: defining the interface to a VI requires values to be instantiated for each type used in the interface. Said another way, you need to plop down controls for each type you want to wire up to your connector pane. If your type is private, you can't drop a control on a VI outside of the library.

So if I adopt this method, I can't pass my singleton to sub VIs. This was brought up by Tomi in response to AQ's 2006 post, and the issue I believe still stands. Checking in the singleton only to recheck it out in the subVI is not an answer as it exposes your code to race conditions, the only way around is to wrap the call in yet another layer of mutual exclusion.

AQ's 2009 post really is not a singleton. In fact I see the DVR as being completely superfluous: if you're just going to copy the value in to or out of the DVR, why not have the value in the LV2G instead of the DVR (the path value that is)? Its fraught with race conditions since there's no locking mechanism in place. Eliminate the DVR and you're left with a static class property via the unerlying LV2G. Replace the DVR with a SEQ, or simply operate directly on the DVR stored in the LV2G (get rid of the get/set methods which copy the value) and you have concurrency solved while you access that path value. But you still don't have a singleton. Nothing is stopping me from dropping another path constant on the diagram and going from there.

I don't think it would be that hard to have a true singleton in LabVIEW if one change was made to how LabVIEW works. If you could somehow tag a VI as "in place" such that it would only operate on buffers that are owned by a parent VI. This ability seems to almost exist as of LabVIEW 2010 where you can tag a VI as inline, but correct me if I'm wrong, you still can't drop a private control on the front panel of an inline VI, as you are still able to run these VIs on their own. That is even for an inline VI, it still creates a buffer for whatever type such that the VI can be run via the panel.

-m

Link to comment

I don't think it would be that hard to have a true singleton in LabVIEW if one change was made to how LabVIEW works. If you could somehow tag a VI as "in place" such that it would only operate on buffers that are owned by a parent VI. This ability seems to almost exist as of LabVIEW 2010 where you can tag a VI as inline, but correct me if I'm wrong, you still can't drop a private control on the front panel of an inline VI, as you are still able to run these VIs on their own. That is even for an inline VI, it still creates a buffer for whatever type such that the VI can be run via the panel.

I take this statement back. It would be fundamentally impossible without breaking the data-flow paradigm.

Consider the screenshot below:

post-11742-0-23915700-1298749232_thumb.p

The class returned from the VI is owned by a library, and the class is marked private. The screenshot demonstrates that the scope of the class isn't really private, as splitting the wire must produce a copy of the value. Conclusion: even marking a class private does not keep new instances of the value from being created (though it does make it harder to do since you can't pass the class to subVIs).

So even if there was a way to mark a subVI as in-place (or a pair of connectors for that matter), the ability to simply split a wire would still work against the whole singleton idea as it would still be trivial to create multiple values of the singleton object.

Link to comment

In fact I see the DVR as being completely superfluous: if you're just going to copy the value in to or out of the DVR... <snip> ...Its fraught with race conditions since there's no locking mechanism in place.

Yes, good point - but I think the 'race condition' issue is just a use-case issue rather than a design flaw.

All transactions that occur inside the IPE are atomic, so you are safe if you create a 'Singleton' methods for everything you want to do and its done inside the IPE.

If this is not flexible enough then don't use it, instead pass a DVR around whereby the developer has the choice to implement the lock-and-unlock paradigm and select and run methods where required.

But I have also seen the 'same' race-condition is other's code when an API is created for a DVR'd Class - which is why I see it as a programming\use-case issue rather than design flaw.

This is also (one of?) the reasons why NI would not allow wiring a DVR to a Class Method (PN only not IN) for fear of creating issues with unaware developers. Whilst I would still like the feature, I have seen it done 'accidentally' by experienced developers, so can now definitely see where NI are coming from.

why not have the value in the LV2G instead of the DVR (the path value that is)?

The advantage of the patten is that the code required is much simpler and cleaner than using a traditional FGV.

When I used to use FGV\AE\MFVIs a lot (pre-LVOOP) I implemented them as follows.

  • I don't like placing enums (methods\commands) down on the BD
  • The enum should be private so private methods cannot be called
  • If the enum gets updated, I don't want it affecting application code
  • So I wrap each method call to the FGV in a wrapper VI
  • This means I can also defined the data inputs/outputs of the method, rather assume user has intimate knowledge of the FGV
  • I usually like to maintain an interface to the FGV which is a Cluster input and Cluster output so that I don't fill up the connector pane
  • So in the wrapper VI I have to bundle up inputs and unbundle outputs

All the above work in not required in the AQ's 2009 implementation, no enums, bundling, or wrapper VIs.

To handle reuse between different singleton methods I create method VIs of the DVR'd Class.

AQ's 2009 post really is not a singleton.

I am not really fussed whether it is technically a singleton or not, only that if the pattern fits my use case, then I like to use it. rolleyes.gif

Link to comment

AQ's 2009 post really is not a singleton... Nothing is stopping me from dropping another path constant on the diagram and going from there.

I'm curious why you say it isn't really a singleton? It has all the same behaviors as a singleton. At most LV will allocate enough memory for two instances of the class type--one that never changes for default singleton objects and one that the DVR refnum points to that all the singleton objects read and write to.

Its fraught with race conditions since there's no locking mechanism in place.

I'll have to agree with Jon on this. Any singleton is going to have to implement a lock/unlock mechanism if users are doing read-modify-write operations. IMO, exposing the DVR (or queue) refnum via accessors is leaking too much of the class' implementation. If you need to do read-modify-write operations on your singleton I think adding Lock and Unlock methods is a much more robust solution.

Link to comment

The advantage of the patten is that the code required is much simpler and cleaner than using a traditional FGV.

When I used to use FGV\AE\MFVIs a lot (pre-LVOOP) I implemented them as follows.

  • I don't like placing enums (methods\commands) down on the BD
  • The enum should be private so private methods cannot be called
  • If the enum gets updated, I don't want it affecting application code
  • So I wrap each method call to the FGV in a wrapper VI
  • This means I can also defined the data inputs/outputs of the method, rather assume user has intimate knowledge of the FGV
  • I usually like to maintain an interface to the FGV which is a Cluster input and Cluster output so that I don't fill up the connector pane
  • So in the wrapper VI I have to bundle up inputs and unbundle outputs
Good points. I do indeed like the lack of enumerations for the calling code.

I'm curious why you say it isn't really a singleton? It has all the same behaviors as a singleton...
Consider two tasks trying to access the 2009 implementation:
  1. Task A obtains the value via the get method. Once returned, there is no active lock on the underlying value.
  2. Task B now obtains it's copy of the value. Note at this point I could create an unbounded number of values. We do not have a singleton.
  3. Task A operates on the value it obtained.
  4. Task A writes the value via the set method.
  5. Task B now has an out of date value.

Essentially the 2009 implementation is a static property, with all the same synchronization issues that arise in any language when you use them. I'd go so far as to say it's over implemented, as had the raw DVR been exposed, at least then I could take advantage of the locking mechanism of the IPE structure whenever I need to operate on the value.

...Any singleton is going to have to implement a lock/unlock mechanism if users are doing read-modify-write operations.

Agreed.

Link to comment

Essentially the 2009 implementation is a static property, with all the same synchronization issues that arise in any language when you use them.

I agree... which leads me to believe your point has completely passed me by. If the LV implementation behaves the same way as singleton implementations in other languages, why isn't the LV version a singleton? Or maybe I'm missing some of the finer points of what it means to be a singleton? (I've not used them much in any language, and I'm certainly no authority on them.)

Link to comment

OK, I see where you're coming from.

For me there's a very distinct difference between a static property, and a singleton. A static property is a variable that is scoped to the underlying class, that is each instance does not have its own copy of the variable. In LabVIEW this is achieved by having an LV2G attached to the class (or any library). This is distinct from an instance property, which would be a variable held in the instance's private data cluster: each instance of the class has its own copy of the variable.

A singleton on the other hand, is an attribute of the underlying class-- not the value. It's a pattern that ensures there will only ever be a single instance of a given class/type. At least that has been my understanding over the years.

In computer engineering, the singleton pattern is a design pattern used to implement the mathematical concept of a singleton, by restricting the instantiation of a class to one object. (Wikipedia)

I have only used them in languages like C++ where you need to have all forms of constructors marked private, preventing the user from creating any new instances, either explicitly or via copying. The inability to make this restriction in LabVIEW is why I don't think a singleton class or library is possible*.

I think the closest we're going to come to a singleton in LabVIEW is a DVR refnum held in an LV2G. The DVR ensures that no two processes will be able to have the value concurrently. A SEQ would work as well, but I dislike that implementation because if the calling code fails to check the value back in, you get a deadlock the next time you try to use it.

*Granted a close approximation can be made as mentioned above using AQ's 2006 example. However copies can still be made via splitting of the wire, and passing the singleton to subVIs is impossible, dramatically limiting the use of these objects.

Link to comment

It's a pattern that ensures there will only ever be a single instance of a given class/type.

By "instance" I'm guessing that you're referring to the memory allocated that stores the actual user data, right? Is it okay to obtain multiple pointers to the singleton? I found a couple different c++ singleton implementations (here, here, and here) and if I'm reading them right (big 'if,' it's been 15+ years since I've written a lick of c++ code) it looks like they're doing essentially the same thing AQ is doing--storing the data in some sort of static member (dvr, queue, fg...) and initializing it on the first call.

However copies can still be made [with AQ's 2009 example] via splitting of the wire...

You can split the wire, but in AQ's implementation there aren't going to be any copies. None of the class.ctl data is set to anything other than the default value... ever. Since all instances of the class always have default values, they all point back to the memory location set up for the default object. The first time a class method is called the DVR gets initialized and a second copy of the class.ctl data has to be allocated, but there will never be more than two, and only the second one will ever be accessable by the objects.

and passing the singleton to subVIs is impossible, dramatically limiting the use of these objects.

I must have missed this earlier in the thread... I'll have to try and track down the reasoning tomorrow.

Link to comment

Ok, back on a PC - here it is...VI Server is the issue.

Jon - My interpretatin of that statement was that this applied to the updated 8.6 implementation, and actually any implememntation that "checks out " the class and passes the class wire between class methods. This would be a problem in the 8.2 version as well. It also had checkout/checkin components, though the actual usage example didn't use them and was "wireless".

I think there is cross-talk between the two implementations "8.2, 8.6" and 2009! :)

Indeed. But I like the discussion and will need to catch up on the thread before putting in my $.02.

-Scott

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.