08-16-2012 05:59 PM
DavidStaab wrote:
The longer I write software, the more I become convinced that internal storage of any state is a generally terrible idea. Scope it privately it and push it all up to the caller for storage!
Agreed. As a general rule I avoid having individual vis maintain state information between calls as I think it causes more problems than it's worth.
DavidStaab wrote:
If you write an Actor, you have to assume it's going to be multi-instanced.
Not necessarily. If you're exposing actors publicly to code you don't control or plan on making it part of your reuse library, then yes, it's probably a good idea to assume there will be multiple instances. If the actor is a private member of an application specific lvlib and you only launch one copy of it, I think it would be safe to assume there will not be multiple instances.
In the couple years I've done actor-oriented programming very rarely have I ever needed multiple instances of an actor. I don't know if it's because I work on different kinds of applications, break down an application differently, or use a different actor paradigm. It would be interesting to hear what kinds of tasks people are doing with multi-instance actors.
Regarding rules of thumb, I agree with LVB and soupy--keep it as simple as possible. However, once you decide to support multiple instances I would make all the methods reentrant, even the initialization and shutdown vis. It's not a matter of performance, it's allowing each actor instance to execute completely independently of the others. The question of whether to use shared or non-shared clones comes down to whether you are hitting the limits of processor cycles (use non-shared clones) or the limits of available memory (use shared clones.)
08-16-2012 06:01 PM
DavidStaab wrote:
The longer I write software, the more I become convinced that internal storage of any state is a generally terrible idea. Scope it privately it and push it all up to the caller for storage!
Haleluja, ladies and gentlemen, another soul saved!
(But, just because it is a terrible idea doesn't mean I don't occassionally do it. It is extremely expedient. Like most sins, if there weren't an upside to it, they wouldn't call it "temptation".)
Next week's sermon will be titled "Fire, brimstone and shared by-reference data".
08-16-2012 06:22 PM
AristosQueue wrote:
The guidance for which methods to make which kind of reentrancy is the same as for any other VI. There is no difference.
So here's where I got messed up: a message class has the same name as its object.
I looked at Awesome Msg.lvclass in my project and thought, "Man, all these method VIs belong to Awesome. If I need to launch four copies of Awesome, I'm going to have four copies of each method." WRONG! I forgot that Awesome.lvclass is just the type definition for four awesome objects. The method VIs only "belong" to the class insofar as they're allowed to access its private data; their memory space and lifetimes are unrelated to whatever objects are passed into and out of them.
So the reality is this: If I have four objects and call one of my Awesome methods on them, it looks like:
There's only one instance of Be.vi in memory, even though four awesome messages called it from their Do.vi's. So, as you explained, unless simultaneous execution is critical, it's safe to leave stateless message methods non-reentrant.
But I think there's a corner case: If the message method requires unique state for each object it operates on, then that state needs to be stored in the object. This looks like the only way to guarantee that state for that VI isn't commingled among its calls. (Example: if the VI conditionally increments an internal counter when it's called, then the counter value is unique to the object it's operating on, not to the VI itself.)
08-16-2012 06:29 PM
DavidStaab wrote:
But I think there's a corner case: If the message method requires unique state for each object it operates on, then that state needs to be stored in the object. This looks like the only way to guarantee that state for that VI isn't commingled among its calls. (Example: if the VI conditionally increments an internal counter when it's called, then the counter value is unique to the object it's operating on, not to the VI itself.)
This is 100% correct unless you're trying to count how many times any message triggered the particular method, in which case you could put that count as a local storage in the handling method.
08-17-2012 07:18 AM
DavidStaab wrote:
If you write an Actor, you have to assume it's going to be multi-instanced.
I've recently been spending quite a bit of time with this key concept. When I started out with the AF I was initially a little disappointed that *everything* was reentrant... If nothing else it makes debugging a pain, right?!
In reality, most of my applications are composed almost entirely of single-instance actors. A typical example would be heavily UI oriented projects where there is a definite calling heirarchy, but where each UI is a one-off. There have been a few moments where I've realised "Hey - I can open two of these results windows side by side!", but if I had to choose between a single-instances-only AF and a fully-reentrant AF then I'd probably prefer the former for many of my use cases.
And so (like any good engineer would do) I've started investigating hacks to make the AF work with both reentrant and non-reentrant actors. (Kind of a challenge to see how easy I can make it! ) Obviously much of the core infrastructure is still heavily reentrant, but I've had some decent success with non-reentrant "leaf" actors.
Has anyone else dabbled with this idea?
08-17-2012 09:41 AM
fabric wrote:
Has anyone else dabbled with [non-reentrant actors]?
Yes, though not with the AF. Once you hack an api released by NI you're kind of committing yourself to rehacking it for every LV release over the life of your project. Plus unless you changed the namespace of your hacked version you run the risk of conflicts between the released version and the hack. I'd rather not take the risk.
Over the past couple years I've developed my own non-reentrant actor implementations and used them successfully on several projects. Like you, nearly all of my actors have only a single instance. I have found that as projects requirements evolve any single loop could eventually become an actor, so I've tried to make the refactoring steps small and natural for transitioning a loop from a non-actor into an actor. There is no framework to speak of (although I often use LapDog for messaging)--it's really just recognizing the essence of what it means to be an actor and refactoring towards that.
08-17-2012 10:53 AM
fabric wrote:
Has anyone else dabbled with [non-reentrant actors]?
I have, when we first made the AF. Then we made it parallel to handle a wider set of cases. I still use non-reentrant for most individual methods on classes, especially when I expect them to be singletons. Since I rarely have to drill into the framework VIs themselves, I don't much care what they do, except for Actor Core.vi. That one can be solved by taking the contents of your Actor Core.vi and selecting it and doing "Create SubVI"... now you don't have a reentrancy problem and you haven't changed the framework.
08-17-2012 04:25 PM
FYI to those reading this thread: I got burned exactly as expected, but it was using vi.lib functions and not my own. Here's an idea exchange post detailing the problem (and, with your vote, asking NI to fix it.): http://forums.ni.com/t5/LabVIEW-Idea-Exchange/Stateless-Mathematics-and-Signal-Processing-VIs/idi-p/...
08-17-2012 06:02 PM
Daklu wrote:
Once you hack an api released by NI you're kind of committing yourself to rehacking it for every LV release over the life of your project. Plus unless you changed the namespace of your hacked version you run the risk of conflicts between the released version and the hack. I'd rather not take the risk.
...which is why I made *my* own framework too! But that's another post...
08-17-2012 06:20 PM
AristosQueue wrote:
Since I rarely have to drill into the framework VIs themselves, I don't much care what they do, except for Actor Core.vi. That one can be solved by taking the contents of your Actor Core.vi and selecting it and doing "Create SubVI"... now you don't have a reentrancy problem and you haven't changed the framework.
Yep - that does the trick for any typical "workhorse" actor. Unfortunately it is not so simple in many use cases. Take the UI example: The top-level of AC is usually running a parallel loop containing an event structure... With these type of situations I found it reasonably safe to modify "Actor.vi" to call EITHER Actor Core.vi or "Actor1 Core.vi", where Actor1 is a non-reentrant version (*replacing* the normal AC) which then calls the AC of its parent and things go back to normal from there up the call chain.
Not sure if you'd ever consider adding such a capability to the framework, but I do think there are plenty of people out there who work primarily with singletons.
...either that or we all start to get behind some ideas for better debugging tools for clones!