Jump to content

DVRs for accessing class private data


Recommended Posts

Hello all! I am new to LAVA, but I have been lurking for many years and have gotten a lot of great advice and insight into LabVIEW programming here. I recently got my CLD, and I'm trying to get a better handle on doing things the Right way.

 

One issue I run into regularly is how to deal with class private data. I typically use device classes, where each class represents a different embedded device that my application communicates with. One main loop is used to send/receive data from each device, and then other loops handle the UI updates, background calculations, etc. I think that is all pretty standard.

 

Due to the by-value nature of wires, I cannot create a device class and pass it to several loops without forking the device private data. When the main update loop updates the class's private data, all other loops do not see this change due to the class having been forked. So what I've ended up doing is putting DVR references in each class's private data, and then the get/set accessor VIs use an IPE structure to access the data via the DVR ref.

 

This all works pretty well, but on many forums (especially NI forums) I see seasoned LV programmers saying that DVRs + IPE should almost never be used. I've considered using FGVs instead of the DVR, but that seems similar in that the data would be stored in the FGV instead of the class's private data DVR, so what is the difference?

 

I feel like I must be missing something here. It seems that class private data should be a DVR by default, otherwise what is the point if the class wire is forked? How do you guys deal with this issue?

Link to comment

What you describe is exactly how I deal with objects I want to share. Yeah, you have to consider the ramifications of multi-threaded access and managing what occurs in the IPE node (I almost never dynamic dispatch in there to protect against deadlocks in a future derived class) and you have to be sure you have captured your atomic activities either through a DVR or an internal static / non-re-entrant member of some kind.

 

But I only go with DVR route when I know that sharing is an objective; it's never the default.

Link to comment

I also use DVRs inside my object when I know I'm going to fork it. If you want to avoid DVR, I guess you can us a SEQ (Single Element Queue). Dequeue the object to "check out" and re-enqueue it to "check in". While the queue is empty, the next place that needs to R/W the object will wait inside its dequeue (as long as you let the timeout to -1 or make it long enough).

Link to comment

I rarely use them, but that's because I rarely share by-ref data between loops, by any method (I use messages, instead).

 

Interesting. I hadn't considered using messages only because I am typically sharing arrays of numeric data. I'm assuming you aren't passing around arrays of data via messages, so how is that particular case handled? How big does the message data have to get before it starts significantly impacting performance? (i.e., if I pass a cluster of N elements as message data, how big does N have to get before seeing performance hits in a typical application)?

 

I also use DVRs inside my object when I know I'm going to fork it. If you want to avoid DVR, I guess you can us a SEQ (Single Element Queue). Dequeue the object to "check out" and re-enqueue it to "check in". While the queue is empty, the next place that needs to R/W the object will wait inside its dequeue (as long as you let the timeout to -1 or make it long enough).

 

That is interesting, especially because it seems that it would make debugging easier (deadlock time can be monitored by looking at the queue size).

 

But I only go with DVR route when I know that sharing is an objective; it's never the default.

 

Hmmm, I can't think of a single case where I didn't want to share data!

Link to comment

 How big does the message data have to get before it starts significantly impacting performance?

Messaging doesn’t actually cost anything (it’s just a pointer under the hood); it’s copying that can be expensive.   If you pipeline, with A collecting data to pass to B (without A keeping a copy itself), then there isn’t any copying.  Similarly, if you share data between loops with a DVR, but make a copy somewhere (to update an indicator, say), then you have the same overhead as if you had made a copy to send in a message.

Link to comment

I feel like I must be missing something here. It seems that class private data should be a DVR by default, otherwise what is the point if the class wire is forked? How do you guys deal with this issue?

 

We use Referencing objects mostly, and we use OpenGDS(http://opengds.github.io/) to create and managed  these classes, have you tried this Open Source free tool?

Use the tool to create a GOOP4 class and see how the DVR solution is implemented, it’s pretty straight forward.

This video goes through the DVR design of a lvclass.

https://www.youtube.com/watch?v=UcUj_Gl1JdI

Cheers,

Mike

Link to comment

This all works pretty well, but on many forums (especially NI forums) I see seasoned LV programmers saying that DVRs + IPE should almost never be used. I've considered using FGVs instead of the DVR, but that seems similar in that the data would be stored in the FGV instead of the class's private data DVR, so what is the difference?

 

I feel like I must be missing something here. It seems that class private data should be a DVR by default, otherwise what is the point if the class wire is forked? How do you guys deal with this issue?

The DVR is in the language for a reason and has high value, but don't use it just because you would use a pointer in other languages. The FGV is used for similar purposes but has disadvantages with regards to scalability, lifetime control, and code comprehension. But I also have a huge vendetta against the FGV. Other folks on the forum might say a DVR is just like an FGV with more annoying syntax and useless features.

 

I also use DVRs inside my object when I know I'm going to fork it. If you want to avoid DVR, I guess you can us a SEQ (Single Element Queue). Dequeue the object to "check out" and re-enqueue it to "check in". While the queue is empty, the next place that needs to R/W the object will wait inside its dequeue (as long as you let the timeout to -1 or make it long enough).

 

This can cause deadlocks more easily than a DVR and I believe I've seen aristos post elsewhere that fixing the SEQ is essentially the reason for adding the DVR to the language. The only advantage I know of for the SEQ is that you can make it non-blocking, but of course if its non-blocking you have to be really careful to manage state of the queue. However, I've never used an SEQ as I've only used LV for as long as DVRs have been around.

 

---

 

For your situation, it seems like messaging would be a better solution. For example, you say you want to distribute changes from the UI to multiple loops performing similar operations. You could potentially use an event structure in those loops and register for the front panel events directly (http://zone.ni.com/reference/en-XX/help/371361K-01/lvhowto/dynamic_register_event/). Similarly, you could use (http://zone.ni.com/reference/en-XX/help/371361K-01/glang/set_ctrl_val_by_index/) to directly update the front panel from the different loops. (Note this could work well if you just want to interact with the UI, if the changes are coming from many different sources a more general messaging scheme would be better).

Link to comment

Another option, that I don’t think anyone has mentioned, is using a by-val object, but placing it in a DVR (rather than the DVR in the object.   Then one can use the by-val object directly in one loop, or share it with a DVR among multiple loops.   And you have the ability to call multiple methods on the object within a single IPE structure, which can protect you against race conditions.

Link to comment

We also use many classes where we create a DVR for each copy of an object and allow different sections of code to access the same object and directly call public "ByRef" methods. We find that this work well for certain applications. In the future, we will likely change this to the method that you describe where the object itself is passed "By Value" and its private data is a DVR. This will help us with inheritance and dynamic dispatch for certain drivers.

 

The lack of "Timeout" in the DVR IPE structure is the most frustrating part of that feature to me. During development, those sometimes happen and I would much rather get a specific error code than having my code hanging forever! In that respect, SEQ is superior.

 

The other issue that we have been facing is the lifetime of the DVR. Keep in mind that it is associated with the Top Level VI in the call chain when the "New Data Value Reference" was created and NOT with the VI that actually created it (such as an FGV used to pass the DVR to different section of code). Therefore, if that Top Level VI goes idle, you will lose your DVR because LabVIEW will automatically clean it up even if many other VI's still access the object and even if the Sub VI that called the "New Data Value Reference" is loaded in memory. This is also valid for TCP/IP connections and any other type of reference. You have to be especially careful with this concept if you tend to Start/Stop VIs while your application is running or at shutdown time to stop your VIs in the right order.

 

About messaging, we also use that method but I find that being able to call the object's method directly makes it a lot easier to handle sequential commands and error handling that may be happen during the method's execution.

  • Like 1
Link to comment

You have to be especially careful with this concept if you tend to Start/Stop VIs while your application is running or at shutdown time to stop your VIs in the right order.

 

An advantage of many-to-one messaging is that, if you make the message receiver (the “oneâ€) the owner of the reference, then you don’t have to worry about reference lifetime at all.

About messaging, we also use that method but I find that being able to call the object's method directly makes it a lot easier to handle sequential commands and error handling that may be happen during the method's execution.

The secret there is the ability to wait on the reply to a message, and have any error passed back with the reply.  With synchronous methods to send a message and wait for a reply one can interact with an asynchronous loop just as if it were an object.

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.