Jump to content

LabVIEW OOP By Ref?


Recommended Posts

There are no plans to add this in a future version. It was explicitly rejected as a possible feature during brainstorming... I posted about this in the beta forums, and I *think* I updated the Decisions Behind The Design to talk about this. (If I didn't, remind me to update it soon.)

Can you Stephen please update the document to contain reasons why DVR terminals do not support dynamic dispatching?

Link to comment

I am joining this discussion quite late but here is my 5c.

LabVIEW is a parallel programming language. DVRs allow accessing shared data across parallel processes. Now the most important thing we need to pay attention to is to make sure that in no case, the data referenced by DVR is in a temporary state. That is state transitions of the DVR referenced data must always be atomic. There should never be an operation where operation leaves a DVR referenced state into an intermediate temporary state even for a fraction of a second. If the DVR referenced data can at any time be in an intermediate state, then there is always a possibility that a parallel process accesses the data at that moment and this results in the application malfunction that is close to impossible to debug.

So how this relates to LVOOP by-ref design patterns? Well, it means that the lock for the DVR data across all the inheritance hierarchy must always be kept for the entire period of the whole atomic transaction being processed.

Let's consider the consensus solution on this thread of adding a DVR into the private data of a class. Say class Parent private data contains DVR of some cluster data. Class Child inheriting from Parent contains another DVR of another cluster data. This is in accordance with Kurt's (SciWare's) examples. The problem with this scheme is that DVR's are accessed using in-place memory structure (IMS). In this scheme of holding DVR in classes private data, IMS does not allow us to lock both child DVR and parent DVR at the same moment. As a result we cannot guarantee atomicity of a transaction.

Let's assume Parent has two data fields A and B with initial values a0 and b0.

It is not possible for a child method to execute the following sequence as an atomic transaction:

  1. read field A of parent data by executing a parent data accessor method Read A, get value a0.
  2. do some data processing based on the parent data a0 read on step 1.
  3. modify parent private data field B by writing a value b1 using a data accessor method based on the result of processing of step 2.

At the end of step 1, the parent data becomes unlocked again as the IMS locking the parent data is within the parent accessor method. As a result a parallel process can modify the parent data during step 2 destroying the atomicity of the process 1-3. Say a parallel process can modify parent field A during step 2 to be a1. The parent data is no longer in the state the child method assumes it to be. The final state of parent data would be (a1,b1) and not (a0,b1) as expected by the process 1-3. The state (a1,b1) may not at all be a valid state of the object. As a result of our implementation our object state have become invalid representing possibly something unreal.

You can also have dead-locks with the concensus implementation. Assume a parent method locks parent data, then calls calls a dynamic dispatch method of the parent class that has been overridden by the child class and finally writes data back to DVR unlocking the data. Now the parent method is already locked at this point. The child override method may then try to relock parent by calling parent accessor method. However parent is already locked and dead-lock occurs. Dead-lock can occur even if child method doesn't call parent method. A parallel process may be trying to lock first the child and then the parent data in inverse order. As the order of the locking is inverse from the order of the locking of the first process, we may end up in a situation where one process has locked parent and is waiting for access to child data and another process has locked the child and is waiting an access to the parent data. Dead-lock.

So I would not recommend embedding DVR's to classes private data. Instead I would make all atomic methods to contain DVR's to the class on the input and output terminals, lock the entire hierarchy by a single IMS and do all internal processing using by-value wires. You get the performance of by-value classes. You get the benefits of LV natural parallelism within the class methods. And you can always guarantee transactionality of the DVR referenced state.

Conceptually one can consider DVR referenced states as functional globals with a reference to particular subVI clone instance holding the shift register with some state. As we functional globals, we do not want parallel processes to be able to modify the state at the same time. With DVRs only one transaction VI may be modifying the DVR state at any moment of time. With functional globals only one case of the functional global may be executing at any single moment of time. So when using DVRs, make sure you always modify the DVRs transactionally a singe VI at a time. You need to lock the entire data hierarchy in a single transaction to guarantee this behavior.

AQ: Also, I've had a few people suggest that if one member of your privatedata control is a refnum then all of them should be refnums, so thateverything in the object behaves like a reference. I'm not sure what Ithink of that, but I'll throw it out there for consideration.

Well, at least immutable variables could be used as well, where the variable is never written. For example a file class could contain a path field in addition to file reference. A path field for the file path remains the same until the file is closed, so it would not cause even if it is not a reference. And accessing file path would not require locking any DVRs.

  • Like 1
Link to comment

Hi Tomi,

Thanks for the detailed analysis. Here are my thoughts:

In cases where a child wants to:

  1. lock,
  2. read parent private data,
  3. perform calculations/work,
  4. write to parent private data, and then
  5. unlock,

I would set up a Semaphore/lock specifically for that transaction - i.e. I would not rely on the IPE structures or DVRs for ensuring that the transaction is atomic.

Regarding deadlock, I'm pretty sure that you can avoid this altogether by ensuring that class methods do not ever put methods of their own class inheritance hierarchy inside of IPE structures used to operate on class data.

Did I miss anything?

Thanks,

Link to comment

In cases where a child wants to:

  1. lock,
  2. read parent private data,
  3. perform calculations/work,
  4. write to parent private data, and then
  5. unlock,

I would set up a Semaphore/lock specifically for that transaction - i.e. I would not rely on the IPE structures or DVRs for ensuring that the transaction is atomic.

So you would always, when using DVRs, place a semaphore to the class on the top of the hierarchy and when ever accessing any data in the hierarchy you would lock the whole hierarchy using semaphore?

Why you would not rely on the IPE structures when ensuring transaction atomicity?

Regarding deadlock, I'm pretty sure that you can avoid this altogetherby ensuring that class methods do not ever put methods of their ownclass inheritance hierarchy inside of IPE structures used to operate on class data.

Sometimes we just need to place class methods inside the locking mechanism. If semaphores are used for locking and not IPE, then we can place class methods outside IPEs. Otherwise there are situations where we simply need to place the inside the structure.

  • Like 1
Link to comment
So you would always, when using DVRs, place a semaphore to the class on the top of the hierarchy and when ever accessing any data in the hierarchy you would lock the whole hierarchy using semaphore?

I would only do this in cases where I know that specific data inside the parent needs to remain locked while child methods are being called. This is, of course, very tricky and one would need to watch out for other deadlock possibilities.

Why you would not rely on the IPE structures when ensuring transaction atomicity?

Maybe this would work, but it would require putting a parent method inside an IPE structure on the child method's block diagram. This could result in a deadlock, so you'd have to be very careful (similar to if you create a special Semaphore for the first case, above).

Sometimes we just need to place class methods inside the locking mechanism. If semaphores are used for locking and not IPE, then we can place class methods outside IPEs. Otherwise there are situations where we simply need to place the inside the structure.

Yes, I agree with you. It's all about trade-offs. Personally, I find the DVR-inside-LVOOP solution to have the least development overhead in the widest number of use cases (I especially like that dynamic dispatch can work on any method, unlike with DVRs of LVOOP objects). In those cases where we need locking at all levels of the inheritance hierarchy, we can either put class methods inside IPE Structures and be careful not to deadlock or use an external locking mechanism and be careful not to deadlock.

Link to comment

Sorry, this kind of duplicates what Jim just said.

So you would always, when using DVRs, place a semaphore to the class on the top of the hierarchy and when ever accessing any data in the hierarchy you would lock the whole hierarchy using semaphore?

I think that Jim would consider only implementing the lock-unlock on an as needed basis. I am not sure that all transactions can be generalized as atomic. The approach of the class as a DVR or always using a semaphore for data access clearly serializes access to the class which defeats the parallel nature. Sure this will protect the programmer from potential pitfalls of non-synchronized access, but it can also bottle neck other threads.

'Atomic' transactions are not just limited to intra-class operations. What about an accounting system where assets have to equal liabilities. Adding an asset then has to have a comparable increase in liability. If I were to implement an OO hierarchy on an accounting system, assets and liabilities would not be one class, but I would need to be able to 'freeze' access to the system while a transaction is completed. In those cases I would probably have some 'token' that I can grab from the accounting system and return when my transaction was done.

I would implement the same approach for intra-class transactions. This could get pretty complicated as some transactions are independent (multiple transactions and on semaphore) and others are interleaved (one transaction multiple semaphores - if that is possible). Or I can make my life simple and have only one semaphore for the entire class - invoking as needed.

If LV2009 supported some form of dynamic dispatch on a DVR, I do not think we would be having this discussion. It would probably be the opposite - how do I let other threads have asynchronous access to other (read only) methods in my DVR'd class hierarchy.

I choose to go to the DVR'd data member route because the programming overhead (time and complexity) to implement a framework that uses class DVRs that supported dynamic dispatch was not appealing. And dynamic dispatch was more important to me than protecting myself from the complexities of multi-threaded programming.

Edited by kugr
  • Like 1
Link to comment

If LV2009 supported some form of dynamic dispatch on a DVR, I do not think we would be having this discussion. It would probably be the opposite - how do I let other threads have asynchronous access to other (read only) methods in my DVR'd class hierarchy.

So why don't we simply automate the creation of the dynamic dispatch methods corresponding dynamic dispatch by-value method by scripting. Say you could have a virtual folder in your class and a scripting tool that would automatically synchronize the folder content so that it would create a corresponding DVR method for each non-override dynamic dispatch method in the folder.

Tomi

Link to comment

So why don't we simply automate the creation of the dynamic dispatch methods corresponding dynamic dispatch by-value method by scripting. Say you could have a virtual folder in your class and a scripting tool that would automatically synchronize the folder content so that it would create a corresponding DVR method for each non-override dynamic dispatch method in the folder.

Tomi

If I am understanding your approach, the script would essentially automate the creation of wrappers. Interesting.

What would you do with the override dynamic dispatch methods in the folder? Handled at the parent's virtual folder?

So, would my main vi wire up the method that has a DVR terminal?

Link to comment

Hi Tomi

Nice to see you back in the ring, brilliant analysis, you found a couple of pitfalls that we need to look out for or address.

One idea for handling atomic transactions that involve parent and child is to have a method called GetDVR, this just returns the DVR of the object. In those child methods that need to be atomic in its data transactions involving parent and child simply wire the parent DVR and the child DVR to the IPE, or have the parent IPE outside and child IPE inside. Now you have locked both parent and child data while you do your operation. It may be a little inelegant but it gets you around that particular problem.

I also like Jim's idea of a semaphore, I'll have a look at that. Or perhaps a SEQ that acts as a token, as Kugr mentioned.

I view it as child needs to be able to lock parent, but parent shouldn't lock child. So having separate DVR refs for parent and child has its advantages. I can imagine where you could have an active object that has a process running for parent and one for child, and example could be a specific server class. The Parent process manages connections, tickles to clients and sending and receiving messages. The Child process acts on the commands recieved and performs actions and sends responses. You wouldn't want parent methods locking the child, but you may want to lock the parent from the child.

We need a by ref solution that is easy to implement but also easy to distribute. If I make a OOP by value driver for some instrument then I don't expect that the end user needs to be an OOP expert just to implement it into their code. I would expect that they are familiar with things such as File IO, Queues, Visa, DAQmx tasks, VIserver where they have a ref to something and they just perform methods on that ref. I can imagine distributing a driver that was LVOOP inside DVR and then users getting frustrated and confused that they have to start placing IPE structures within their code just to use your driver, to them it wouldn't be consistent with their experience and in many cases outside their level of understanding.

Link to comment
  • 1 month later...
I view it as child needs to be able to lock parent, but parent shouldn't lock child.
If you're facing this situation, I think the correct answer is to eliminate the inheritance relationship in favor of a container class that contains both "parent" and "child" as separate internal objects. Then you are back to only having mutex locking on an entire object.
Link to comment
  • 1 month later...

Can you Stephen please update the document to contain reasons why DVR terminals do not support dynamic dispatching?

I never did get around to updating the white paper, but I typed this up today:

http://forums.ni.com/t5/LabVIEW-Idea-Exchange/Have-Dynamic-Dispatching-terminals-except-Data-Value-references/idc-p/1054548#C3855

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.