LabVIEW Idea Exchange

cancel
Showing results for 
Search instead for 
Did you mean: 
0 Kudos
sbus

Add "anchoring" capability to locals and globals to eliminate race conditions...

Status: Declined

Any idea that has not received any kudos within a year after posting will be automatically declined. 

I would love to be able to right click on a local or a global variable on the BD and select "add anchors" to cause the object to change into one that looked like a value property node, with standard error in/out connections. The resulting object would simply pass error in to error out, but would delay execution until error in was available thereby allowing me to synchronously determine when the value would be evaluated, and eliminating the race conditions associated with local and global variables.

 

Aren't we supposed to be race-neutral anyway?

 

20 Comments
AristosQueue (NI)
NI Employee (retired)

Functional global variables are WORSE than global VIs. They have all the global VI downsides plus less performance.

Action Engines are good-ish. They encapsulate functionality well, but still are static global declarations.

 

The difference between an AE and an FGV? An AE either does not have an action for "get" (and ideally no "set" either) or, if it does have such an action, that action is called once at the end of the FGV's lifetime. It manages some larger data structure behind the scenes.

 

But having global static declarations still causes lots of problems: thread locking, inability to create multiple independent parallel copies of the code, unintended state leakage creating dependencies between callers... they are variable-ish, and so while I put them in the "good usage" list, their status is very provisional.

SteenSchmidt
Trusted Enthusiast

AQ: For curiosity's sake, which category do you put VI Registers in? I'd guess you haven't tried them (you have a few other things to attend to than try out a squillion 3rd party toolsets), but the basic premise is that each read and write VI is inlined and each holds a copy of the queue refnum they use (one unnamed queue refnum per named VI Register). Only in case that local queue refnum goes bad a centrol repo (i.e. an AE) is contacted to get a fresh copy of that refnum.

 

That a central repo is the store for the queue refnums means the refnum lifetimes are kept in check, and I can signal configuration changes to a VI Register by making the AE release that queue refnum, take out the remaining queue content, obtain a new refnum and put the old content into that. That's all encapsulated inside the same AE method, so that when a local read or write discovers its queue refnum has gone bad it'll just ask for it again, get the new object containing the new refnum (and new configuration), cache that for speedy use and continue on as if nothing happened.

 

/Steen

CLA, CTA, CLED & LabVIEW Champion
AristosQueue (NI)
NI Employee (retired)

Steen:

I have looked at it, but minimally. I glanced back through it today just to refresh my memory.

 

For the "Registers" themselves,

a) they do not seem to have any ability to do a read-modify-write lock

b) they do a full data copy on read (i.e. if you do two reads in a row you get the same value rather than locking and waiting for new input)

c) they only support Variant

 

Compared to Notifiers, these seem pretty weak. I'm having a hard time seeing any use cases for them that I wouldn't rather use another of LabVIEW's systems (and that's assuming I had decided I needed a global data store in the first place).  The lifetime management for named Notifiers should -- as far as I can tell -- obviate the need for the lifetime management that you're doing in your VIs. I don't understand why the lifetime management for the Notifier refnums -- which is NOT the same as the lifetime for the underlying notifier, as the notifier sticks around as long as there are any refnums pointing at it -- would be insufficient to do exactly what you're trying to do with Registers.

 

Notifiers have the history ability. If you don't want the history ability, use Get Notifier Status instead of Wait For Notification. If you want, you can combine Wait For Notification with a Feedback Node so you can use the previous value any time the Notifier times out.

SteenSchmidt
Trusted Enthusiast

@AristosQueue (NI) wrote:

For the "Registers" themselves,

a) they do not seem to have any ability to do a read-modify-write lock

b) they do a full data copy on read (i.e. if you do two reads in a row you get the same value rather than locking and waiting for new input)

c) they only support Variant

 


a) They will have locking functionality in the newest release (the one I depicted a palette of in this thread, but which is only released internally to GPower currently).

 

b) As per above, locking will be possible. It is also possible to use buffered reads with a timeout (optionally set to -1 to wait indefinetely). So it's the user's choice to either immediately get the current value, or create a buffered register (FIFO, LIFO, or circular are the options). The full data copy is up to the LabVIEW compiler basically - the register Read VI is inlined and it basically only does either a Preview Queue Element or a Dequeue depending on VI Register configuration.

 

c) They only support Variant so we don't have to make 150-200 VIs more for this toolset. If we had an official Generic input VI Registers could support any native data type. Preliminary benchmarks tell me that native data types will be about 3x faster than Variants+conversion, but we don't want to maintain that many extra VIs...


@AristosQueue (NI) wrote:

Compared to Notifiers, these seem pretty weak. I'm having a hard time seeing any use cases for them that I wouldn't rather use another of LabVIEW's systems (and that's assuming I had decided I needed a global data store in the first place).

 


VI Registers are for the situations where you need a global data store, and no other scenarios. But the users can decide exactly the scope of that "globality" (the toolset includes functions for namespacing and such, so you don't need a library to do namespacing ;-). They should definetely not replace wires, and they do not replace any of the built-in tech (they are built on native LabVIEW queues afterall). VI Registers are for when the built-in tech is hard or cumbersome to do right - exactly as the Actor Framework. It's not something you wouldn't be able to do by other means, but it does the details exactly right.

 

You bring up notifier refnum lifetime management. A notifier lives its life just as dangerously as a queue (obviously); if you obtain a notifier refnum in VI A, and VI A goes idle, that notifier refnum gets invalidated. The refnum management we do in VI Registers makes this point moot. If you have ever in your LabVIEW instance created a VI Register, and all your VIs stop running - If you then start another unrelated VI, you will be able to read from that VI Register you created earlier, without any values being lost in the meantime. VI Registers are as persistent as front panel control values.

 

Of course VI Registers use a demon behind the scenes that owns the VIR refnums so they aren't invalidated (unless you explicitly release them), but that's a detail that we sometimes see LabVIEW users struggle with. They do an obtain (queue or notifier) refnum and then they use that - all within a single VI, which of course works perfectly. Then their architecture grows, and at some point the obtain and usage code gets split into separate VIs that are part of a factory/plug-in architecture where VIs run and go idle all around you. Suddenly you get periodic failures due to invalid refnums, since someone dropped the baton (the obtainer went idle before another user grabbed that refnum - BAM, LabVIEW cleaned the refnum up). As I said, this is one of the key features VI Registers takes care of - a true local or global (by looks) without binding to any control or global VI.

 

Notifiers and queues are just two sides of the same story (I'm not telling you this, just getting it out of the way ;-). Behind the curtains they are the same beast, their APIs just differ in the details. You could definetely do the same with notifiers and queues as we do with VI Registers, it would just take a lot of code and care that you don't have to do yourself now. What we have built around the queues are the things listed below here - and then its of course up to the users to decide if they have any use cases for queues with these additional features:

 

- Refnum lifetime guarantee (as described above).

- Event generation (neither queues nor notifiers can do this yet).

- Access locking (in next upcoming release).

- Similar resource footprint as queues (due to inlining and feedback node caching of the refnums). Weren't it for the Variant data type performance differences between queues and VI Registers wouldn't be measureable - but LabVIEW does not let us do anything about that for now.

- All the nitty gritty details of implementing a current value register on top of a queue (have you seen the NI CVT toolset that is so popular? It's far inferior in performance to VI Registers).

All the nitty gritty details of implementing a buffer, with just a single edit point (an enum) in your application to change that buffer between FIFI, LIFO and circular behavior. FIFO and LIFO are easyish with queues, circular buffers are harder.

 

So those are the main reasons for VI Registers.

 

/Steen

CLA, CTA, CLED & LabVIEW Champion
AristosQueue (NI)
NI Employee (retired)

> If you have ever in your LabVIEW instance created a VI Register, and all your VIs stop running - If

> you then start another unrelated VI, you will be able to read from that VI Register you created

> earlier, without any values being lost in the meantime. VI Registers are as persistent as front

> panel control values.

 

Ah. That is a level of lifetime control I had not realized you supported. I missed that aspect of your API.

 

If you want that kind of coordination between VIs, I can see that being useful; I've never had an application where my top-level VI didn't stay running the whole time... if my top VI ever shuts down and stuff needs to persist, it gets written to a config file. I shy away from uninitialized shift registers for just about everything; I generally don't *want* anything to persist run-over-run unless it is part of the save/load of configuration.

 

> (have you seen the NI CVT toolset that is so popular? It's far inferior in performance to VI Registers).

 

I believe I would agree with this, but I have minimal experience with either. '

 

> FIFO and LIFO are easyish with queues, circular buffers are harder.

 

What exactly does the circular buffer setting do? Under the hood, the queues are a circular buffer. I didn't see any of your APIs that documented a difference in behavior for circular buffers vs FIFOs.

SteenSchmidt
Trusted Enthusiast

@AristosQueue (NI) wrote:

Ah. That is a level of lifetime control I had not realized you supported. I missed that aspect of your API.

 

If you want that kind of coordination between VIs, I can see that being useful; I've never had an application where my top-level VI didn't stay running the whole time... if my top VI ever shuts down and stuff needs to persist, it gets written to a config file. I shy away from uninitialized shift registers for just about everything; I generally don't *want* anything to persist run-over-run unless it is part of the save/load of configuration.


If your top-level VI owns (creates) all your variables, then most often you wouldn't need to do anything else about the refnums. Users just sometimes make a "ReadConfig" actor or some sort, and then that one ends its life while others are using the refnums ReadConfig created. If the invocation of ReadConfig is sufficiently decoupled from the caller hierarchy those refnums will be invalidated.

Config files are good for some things, but for working variables it'll be quite a big overhead to communicate through a file. You wouldn't consider transforming all cpp-file globals into file IO would you? Those are the kind of variables I see VI Registers being used for. Signalling a global Stop, persisting the most current configuration of some changable parameter, holding some dynamic limit etc. And of course then only in places where you can't just wire that value around, and where you don't already have an event-structure perhaps - and especially for many-to-many r/w.

 

It's quite rare that VI Registers should persist between runs (I've had to implement a few of these to persist data between restarts of Real-Time apps in case you don't reboot the RT-controller), but the lifetime management is meant for the case where your variable creator code can go idle while you still have alive data users.

 


@AristosQueue (NI) wrote:

What exactly does the circular buffer setting do? Under the hood, the queues are a circular buffer. I didn't see any of your APIs that documented a difference in behavior for circular buffers vs FIFOs.

 


The circular setting basically ignores the wait for room in the buffer, it just writes unconditionally and discards the oldest element (Lossy Enqueue Element). FIFO by comparison uses Enqueue Element, and LIFO uses Enqueue Element At Opposite End (the latter two with timeout). From the most current VIR doc:

 

VIRDoc.png

 

Cheers,

Steen

CLA, CTA, CLED & LabVIEW Champion
SteenSchmidt
Trusted Enthusiast

I must add that the VI Register API irks me some in the fact that some of the functions have inputs that are only used for some of the configurations, e.g. the timeout input on the Write function (which is ignored for Circular and Register type VIRs). The different register configurations are only set at the Create-point, and then the user can't tell the difference from "outside" (you'll in some situations get errors if you treat a buffered type as a Register type or vice versa though). Ideally there should've been separate write-functions for those that didn't implement a timeout input.

 

I probably should have put a bit more effort into making a more correct API, but the thing has evolved quite severely over the years and I dare not change it now 😕

 

When the time comes for a total refurbishment it'll be into a new toolset with a new name, which'll leave VI Registers parked in its last version. But we will obviously get a chance to rework all our toolsets in due time...

 

/Steen

CLA, CTA, CLED & LabVIEW Champion
AristosQueue (NI)
NI Employee (retired)

> Config files are good for some things, but for working variables it'll be quite a big

> overhead to communicate through a file. You wouldn't consider transforming all

> cpp-file globals into file IO would you?

 

If the values in those variables were going to persist between runs, yes, yes I would convert them all to file I/O. That's how you persist data between runs in any other programming environment.

 

If I am not going to persist the values between runs then I wouldn't be using them as global variables.

 

> Users just sometimes make a "ReadConfig" actor or some sort, and then that one ends its

> life while others are using the refnums ReadConfig created.

 

The mistake here is in letting any other process have access to the refnums. Either one process is reading and then handing out data to the others or every process is opening its own refnums to the config file/database.

 

> Signalling a global Stop,

 

I would never use these for signaling a global Stop for two reasons.

 

a) We have a team at NI that spent a year working on the question of "What would be the ideal way to stop two parallel While Loops in G if we could add any language feature we wanted?" That conversation ranged far and wide, and the one lesson that unquestionably came out of it: Your stop signal should be embedded in the same communications channel that you are using for any other data that is moving between your loops. I have covered the reasons behind this extensively in some of my NIWeek presentations and in my Potato And Candy Bar presentation. So if you cannot use the same VI Register for all the data and the Stop between your processes, I wouldn't use it for just the Stop.

 

b) My work over the last five years on massively parallel systems has convinced me that there is no such thing as "global Stop". There is only "communicated stop" from process to process -- each one knows how to shut its part of the work down, and there is virtually never a real-world application where all processes get the stop signal simultaneously and just quit arbitrarily. It is much more common (bordering on universal for all but the most trivial apps) for there to be a need to communicate a stop to the top-level processes and then communicate that to child processes. Having a child just up and quit is generally an error condition.

 

> persisting the most current configuration of some changable parameter, holding some

> dynamic limit etc. And of course then only in places where you can't just wire that value

> around, and where you don't already have an event-structure perhaps -

 

For all of these, I would want a way to guarantee "write once only or only one writer".

 

> and especially

> for many-to-many r/w.

 

Yeah. Just use a global VI... add a semaphore if you've got to have a protected section. Complicating it with more API is just fooling yourself about what you're doing, IMHO.

 

 

SteenSchmidt
Trusted Enthusiast

> If the values in those variables were going to persist between runs, yes, yes I would convert them all to file I/O.

 

Yes, I would too, but I'm talking about working variables. Variables you r/w while your app is running. Even persistent data would be in memory while your app is running, and then when the app ends you would persist it to disk. It would be very rare that I for every read would read from disk, and for every write, I would write to disk. I know that would be rare for you as well, considering our discussions about exactly this in the Actor Framework group. Even if data for security reasons should be persisted as soon as possible, it would almost always still be an async actor doing it.

 

So let's forget about persistent data and concentrate on runtime working data.

 

> If I am not going to persist the values between runs then I wouldn't be using them as global variables.

 

I use the term "global" liberally. I mean "global" within a certain scope, i.e. simply "not local". Any data two VIs needs to share (r/w) cannot be local. But I don't necessarily mean global to the entire execution instance. It could be, but most often it's not. Actually quite like cpp file globals - these are accessible to the functions defined in that cpp file, and no one else. If you've grouped your functions in a meaningful way, file globals (in C++) is a perfectly well-behaved scope to use. But this type of scoping isn't unique to VI Registers, you have that everywhere in LabVIEW (see further down).

 

> > Users just sometimes make a "ReadConfig" actor or some sort, and then that one ends its

> > life while others are using the refnums ReadConfig created.

>

> The mistake here is in letting any other process have access to the refnums. Either one process

> is reading and then handing out data to the others or every process is opening its own refnums to the config file/database.

 

Eh, the term "its own refnum" is a bit ambiguous here, or am I mistaken? Do you mean that named queues are evil in that sense that they shouldn't be used to communicate between two VIs? VI Registers are exactly like named queues. They are just harder to kill by accident, and they use up much less BD real estate without yielding on readability.

 

A comparison of queue lifetimes (Init Q refnum does both obtain the queue and enqueue some data in it for dequeueing by Use Q refnum, green VIs are running while gray VIs are idle):

 

Q_Refnum_Lifetimes.png

 

A will work for both named and unnamed queues.

B will not work for unnamed queues, but will work for named queues.

C will not work for either unnamed nor named queues (at 3 data will no longer be available for dequeueing, even for named queues).

 

All three scenarios will work flawlessly with VI Registers. That's the sum of it really.

 

Your stop signal should be embedded in the same communications channel that you are using for any other data that is moving between your loops.

 

I disagree. Or, you could, but you would have to prioritize your messages then. Until LV 2013 you couldn't prioritize events, and to prioritize queue messages you'd have to stuff your high priority message into the opposite end of the queue. For a much simpler architecture I always split up my different priority messages in one message channel per priority. Often that boils down to data in one channel and commands in another. There are all kinds of variations to this of course, but let me illustrate what I mean with the stop signal:

 

Data_Signal_Split.png 

 

Should both Data and Stop? be messages in the same event, then the event type had to be something that needed decoding (which wouldn't make sense). Were the two signals two separate events handled by that event structure, then we'd have to prioritize the events (only works with two priorities, and only from LV 2013). Similar with queues. I say one category of signal per message channel.

 

> [...] my Potato And Candy Bar presentation

 

That has to do with by-ref vs by-val though, not message priority?

 

> My work over the last five years on massively parallel systems has convinced me that there is no such thing as "global Stop".

 

I agree (I have architected and implemented some pretty large systems myself over the years :-). As I wrote at the top of this post I don't mean a "global stop" as in app or instance global, but rather global to a given collection of parallel executing structures. Inside one actor I'd usually have an event handler that eats messages from outside the actor. On the same BD I'd have some helper-loops that each has this or that responsibility. When a Stop command is received (as an event) I'd use a SEQ (Single Element Queue if anybody else are still reading) to carry my stop signal to my helper-loops.

 

Some people will use a Local to carry such a signal to helper loops - and why do they do that? Because it's much simpler to write to and read from a Local than manage a queue. When they have dropped their Locals onto the BD they have locked themselves away from the possibility to move their helper-loops into subVIs though. So, since VI Registers are just pre-packed SEQs (with some useful additional features, such as registering the SEQs for events) I use VI Registers instead of SEQs to carry such signals. VI Registers are just as simple to deploy as Locals (simpler really, as you don't even have to have a control on the front panel for them), but they work equally well on the top BD of the actor as when you decide to embed them into subVIs with the helper-loops.

 

> > and especially for many-to-many r/w.

>

> Yeah. Just use a global VI... add a semaphore if you've got to have a protected section.

> Complicating it with more API is just fooling yourself about what you're doing, IMHO.

 

- A global VI means one more file on disk you need to create and maintain.

- Sequencing a global VI means a sequence structure, while VI Registers can flow on the error wire if necessary.

- A global VI does not have built-in semaphores. VI Registers do.

- A global VI cannot be registered for events. VI Registers can.

 

All the above are details, and nothing that re-invents the wheel. Everything about VI Registers can be done with native LV functions, it just uses up so much more BD-space to do so. And each action the VI Register functions do is common enough to warrant putting it into a re-use subVI. I have done that.

 

One other thing that VI Registers are that global VIs aren't (nor Locals for that matter); VI Registers are dynamically referenced. Read a bunch of values defined by name at runtime:

 

For a global VI (or Local etc.) - and this requires all the global VI fields being predefined at edit-time of course:

 

Read_Many_Globals.png

 

VI Registers can be created and destroyed at runtime, and are referenced by string name:

 

Read_Many_VIRs.png

 

Dynamic referencing is much more flexible than static referencing. It's also more "dangerous", but it's just a reference and it's nothing different than other built-in name-referenced technologies, e.g. named queues, string-referenced VISA actions, FPGA HW-references, the APIs for Shared Variables and Network Streams etc. All string referenced.

 

I feel you're looking at this a bit puritan-like, instead of seeing VI Registers just for what they are; shrink wrapped SEQs with numerous good extra features. Nothing more, but nothing less either?

 

/Steen

CLA, CTA, CLED & LabVIEW Champion
Darren
Proven Zealot
Status changed to: Declined

Any idea that has not received any kudos within a year after posting will be automatically declined.