Jump to content

What so bad about 'thread safe' singletons anyway?


Recommended Posts

Besides retribution from God, it seems that there are some legitimate use cases (at least I hope so since my latest MVC architecture is based on the concept).  In my application, Model data is housed in the DVR wrapped class data of a singleton.

 

post-162-0-06909100-1371304902_thumb.png

 

Various methods within the class allow View instances to perform Read or Read-Modify-Write operations.  RMW operations can be locked using the built-in semaphore.

So far it all seems to work nicely with up to 6 View instances all performing rapid transactions on the Model.  I guess the downside is that a hung View could block access to the Model (maybe I should have put a timeout in the semaphore lock :)

 

Since this is a first attempt please do not be too harsh if I am doing something utterly foolish...

Link to comment

At first I did not have the semaphore but then realized that I might not want to build all forms of R-M-W permutations into specific class methods.  With the semaphore I can have use cases that allow external concatenation of Lock,Read,Modify,Write,Unlock methods without having to change the core Model class.

Link to comment

If I use a Singleton implementation - I generally create a second class (a child class or friend class), or create public functions within the class; to handle the singleton aspect rather then expose the DVR. The advantage to this is, I handle all interactions with the DVR rather then worrying about an external force possibly destroying or somehow stalling the DVR access. The FG containing/creating the DVR is a private VI to the class controlling the singleton. This does mean that every public VI for your regular class that you want the singleton to access must also have a singleton version.

 

I due, in general, try avoid using singleton implementations. My singletons usually relate to log files.

Link to comment

It looks on the surface like you have the LabVIEW equivalent of mixed metaphors. The general method for a singleton pattern is to check in and out rather than for loop memory and semaphores (which is a classic LabVIEW kind of thing you might do in an action engine, although all VIs by default are singletons so you wouldn't need the semaphore)

 

This may help as may the singleton pattern example in the examples dir.

 

 

What so bad about 'thread safe' singletons anyway?

 

You don't have to worry about "threads" or "thread safety" in labview. I think you mean "race free" singletons..

Edited by ShaunR
Link to comment

In my MVC architecture I have many Views all running in separate threads so I needed a way to provide thread safe access to the Model data.  Initially, I created the Model class with the idea that the DVR would be my locking mechanism but soon realized that the 'Modify' logic in some cases was too complex to put into the Model class itself so I needed to add the semaphore to allow the external locking.  I think my BD suggests that the DVR reference is public but actually only Model Class vi's have access to it.  I would be curious to know how others would construct a transactional model that is fast and can conveniently store and retrieve any LV data type.

 

BTW, I think that the prominent feature of 'thread safety' is avoiding race conditions in shared data structures (at least according to wikipedia :)

Link to comment

Yes - I was just trying to be funny but really that is the issue - I am presuming that my method is thread safe but now am concerned that I, like many others that have inspired God's wrath, have been

lulled into a false sense of thread safety.

 

Here is a typical example of how I would do a R-M-W operation:

 

post-162-0-35860100-1371396884.png

 

post-162-0-32920000-1371396912_thumb.png

 

post-162-0-33026100-1371396930_thumb.png

 

Basically this is a cooperative mutex.  Any Model read that intends on using its data to do a subsequent M-W is required to lock the model. 

I think there is a flaw in my code in that a isolated Model write should wait for the mutex semaphore if it has been locked by a R-M-W sequence.

Link to comment
It looks on the surface like you have the LabVIEW equivalent of mixed metaphors. The general method for a singleton pattern is to check in and out rather than for loop memory and semaphores (which is a classic LabVIEW kind of thing you might do in an action engine, although all VIs by default are singletons so you wouldn't need the semaphore)

 

This may help as may the singleton pattern example in the examples dir.

 

 

 

You don't have to worry about "threads" or "thread safety" in labview. I think you mean "race free" singletons..

That article is a bit confusing...the SEQ version seems interesting and has built in mutexing which is nice but I do not understand the warnings about its use.

This helps to clarify the options...

 

https://decibel.ni.com/content/docs/DOC-20865

 

It looks like I probably should just encapsulate all my R-M-W operations into class methods that use the IPE wrapper and get rid of the semaphore mutex

Link to comment
That article is a bit confusing...the SEQ version seems interesting and has built in mutexing which is nice but I do not understand the warnings about its use.

This helps to clarify the options...

 

https://decibel.ni.com/content/docs/DOC-20865

 

It looks like I probably should just encapsulate all my R-M-W operations into class methods that use the IPE wrapper and get rid of the semaphore mutex

Maybe. But you should note that in the presentation, his FGV singleton is a singleton (because  all VIs are singletons-unless re-entrant) but he is incorrect that this is enough to fix the race condition in his anti-pattern (i.e. he hasn't fixed it and it isn't atomic).

The FGV must contain the read->write for it to be protected by the VI boundary

Edited by ShaunR
Link to comment
I think this is what mje was alluding to in his first post. It makes the implementation a bit more simple.

 

Indeed.

 

I'm still not sure I catch what you were originally trying to do. You seemed to be going to a great deal of effort to avoid passing a class wire around in favor of a semaphore and other type definitions along with the class reference.

 

You now seem to be on a different track-- just using traditional class methods and sharing a DVR around via a FGV. Remember what Shaun pointed out, a FGV is a singleton.

 

There are several issues that can creep up with implementations similar to where I think you were going (the DVR + semaphore). These cautionary points aren't necessarily critical of your original strategy as there are far too few details above, rather they're more what I've learned from my own experience. I've made these mistakes before and now settled on not designing classes which use DVRs of their own type on their connector panes. Keep everything by-value. If a framework needs a DVR, be it for a singleton other reasons, keep the DVR entirely out of scope of the class.

 

One reason is data copies. Take a good look at some of the example code posted above and spot where data copies are being made. This may or may not be a problem depending on the nature of your model, but bear in mind the quantities and reentrancy of your class methods also factor in. Even a modest sized model can use orders of magnitude more memory than the actual model size if you have to make copies of it in many VIs, multiply it even more if some of these methods are reentrant and they're used in many places in your code.

 

Using class based DVRs as I/O terminals on the class' own VIs also pretty much kills any idea of extending code as everything needs to be statically linked. Frankly, if you can't use dynamic dispatch, you really just have fancy type definitions and you've more or less neutered the main reason to use objects in my opinion. Yes, I'm oversimplifying the argument, but it's late.

 

I'm already losing my train of though here, so finally I'll just say it's so much simpler if you forget about the DVR entirely when writing your class. I'm not saying ditch the DVR, just don't consider it from the class' perspective. I used to hate the idea of using IPEs everywhere in my code. Granted, I still don't particularly like the aesthetic of them, but they are very functional, and I really think it's not the responsibility of the class in most cases to dictate what needs to be atomic or not. You may think you know what operations need to be atomic now, but if a year later you need to string together a pair of previously atomic operations in a new composite atomic operation, you can't do so without re-factoring the original class. That should set off alarms.

 

m

Link to comment

Thanks all for the good comments.  I am still unsure of a better way to construct a transactional data model that can be used by Multiple View Processes.

Here is my take thus far:

 

1. Since the application requires Multiple View Processes I cannot pass around a class wire so will need interfaces to a central store

2. One of the nice things about the Model Class Data DVR is that you can extend the data model without breaking any wires directly connected to IPE's (I do not like that aesthetic either)

or wrapping the IPE's in class methods and using the Get Ref vi in each case to extract the DVR onto the BD and into a local IPE.

3. The semaphore protection would be nice but seems complex to understand all nuances 

4. A FGV housed Data Model solution by itself would be unwieldy since it is so limited in extensibility. 

5. Will have to study the impact of data copies in my approach, thanks mje

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.