From Friday, April 19th (11:00 PM CDT) through Saturday, April 20th (2:00 PM CDT), 2024, ni.com will undergo system upgrades that may result in temporary service interruption.

We appreciate your patience as we improve our online experience.

Actor Framework Discussions

cancel
Showing results for 
Search instead for 
Did you mean: 

Using Global Variables in the Actor Framework/ IOServer

I have seen in the documentation where it recommends to use Refnums or User Events, but I am still struggling with how to use that exactly (cuz right now my Actors are QSM's). I will update that later, but for now I need to get this to work and using Global Variables is giving me the easy solution.  Other than the fact that I might  change the variables inconsistently, is there an issue with this?  Also, what is the easy way to transition from them?   I plan to break out each state of my QSM into its own class, so that the data is protected, but haven't gotten there yet.   In the meantime, I plan to have an IOServer that takes the Global Variables and reads and writes their values to/from the Registers.  Are there any hints on how to do this right?

0 Kudos
Message 1 of 9
(8,390 Views)

It seems to me you need to make an architecture decision. You can have a central data storage actor that handles access to the Global and receives requests from your other QSM like actors to get/set data. Alternatively you can have decentralized data with actors "owning" their own data and sending messages conaining data that other actors need in a message. AF is a tool to help with messaging (error handling, template for set-up, tear down, etc). Who owns and controls the data is up to you. Think carefully about what your system does, how you are going to extend or modify it, etc.

Casey

Casey Lamers


Phoenix, LLC


casey.lamers@phoenixwi.com


CLA, LabVIEW Champion


Check Out the Software Engineering Processes, Architecture, and Design track at NIWeek. 2018 I guarantee you will learn things you can use daily! I will be presenting!

0 Kudos
Message 2 of 9
(4,609 Views)

Global data is difficult to manage at best, and is especially challenging when changing the variables inconsistently - it results in inconsistent behavior in the application.

You mention that you plan to "break out each state of my QSM into its own class, so that the data is protected" - a state in a QSM is an algorithm, a class encapsulates coherent data and provides appropriate methods to operate on or with that data, so converting an algorithm to a class will result in a confusing implementation.

AF is a tool to help with messaging - it can also help with encapsulation.

As Casey says - you need to make an architecture decision.

0 Kudos
Message 3 of 9
(4,609 Views)

I can help you do the globals right, but to do that, I need to highlight some of the issues that you can run into.

You noted the danger of values changing inconsistently. That's one issue. You also have to worry about inconsistency in the timing of different branches of your code hearing about the change. Most people say that a global that has exactly one writer in the system and N readers is safe because you cannot have the read-modify-write race condition. And that's true. But you can still have:

  1. The polling loop itself can cause performance problems. None of your subsystems can ever really go to sleep -- they have to have some loop regularly polling that global looking for value changes. You can put some artificial "Wait ms" nodes in your code to cut down on the CPU waste, but ultimately, you have a loop that should be asleep but is instead waking up to check if global state has changed. There's just no signal that will wake up sleeping code when a global VI changes values.
  2. A secondary but real performance issue with polling the globals is that every time you read a global VI, you make a data copy. If your data is small, that's not an issue. If your data is large, it can be a problem.
  3. One writer and two readers. The one writer writes a value and then one reader reads it, but the second reader is slow getting around its polling loop... before it can read, the writer writes a second value. Both readers may read this second value. Was it important for every reader to see all the intervening values? In some cases, if the value was some sort of "toggle your hardware" signal, it can be very important.

LabVIEW features that inherently have some sort of handshake or timing built into them are generally superior to the global VIs because code becomes aware of when the rest of the system is changing, not just how it changed.

So if you are going to use global VIs to transmit data across all of your actors, you want to do something that mitigates these risks, if they apply to you.

The easiest way to poll the globals consistently is to add a loop to your Actor Core that takes care of polling the global VIs and then enqueues a message to itself with the changes. "Read the global, compare to last read value, if different then send message, sleep X milliseconds, repeat."

That, obviously, introduces the polling overhead. I can't help you much with problem #1... someone is going to have to be awake to poll that global VI. What I can say is that you can minimize the number of actors that are doing this. Some actors in your system are probably thought of by you as "top-level actors" -- these are the ones that are fully independent and you don't really think of there being a single root actor over all of them. If you limit your polling of the global to only these actors, they can send messages to their nested actors with the relevant bits of info that those nested actors need, thus saving the nested actors from doing their own polling.

For issue #2, you can decrease the performance hit some by having one global value that is just a timestamp -- every time you change the main values, you write a new value to the timestamp. The polling loops just read the timestamp and only read the larger global data when they see the timestamp is different. That cuts down on data copies and the performance overhead of doing a compare.

For issue #3, you hope it doesn't affect you. But if it does, here's my recomendation*: Add a rendevous refnum to your global VI data and initialize it at the start of your program with the number of polling loops you have plus 1 (the plus 1 is for the writer). Whenever you write a new value for your global data, update that timestamp and then wait on the rendezvous. When a polling loop comes through, it reads the timestamp. If the timestamp is different, it reads the global and then waits at the rendezvous. Everyone waits until all polls have the new value, then they all proceed. That keeps everyone sync'd up with the same values of the global data.

Good luck.

* assuming that I'm told I have to use global VIs -- remember that I'm in the camp of "never use them, and if you do, never tell anyone you did so". 🙂

Message 4 of 9
(4,609 Views)

mflegel wrote:

so converting an algorithm to a class will result in a confusing implementation.

Not necessarily... take a look at the state pattern. It's a well-established concept borrowed from other programming environments of changing state by changing the actor itself. There are several systems where this results in code that is significantly easier to understand because it enforces some amount of consistent pattern of action on every state in your machine. It isn't the right choice for most machines, but there are conditions where it becomes the right choice.

0 Kudos
Message 5 of 9
(4,609 Views)

Aren't you overcomplicting things?  Mixing the Actor Framework queued-message handler, and your "QSM" (another type of queued-message handler), and global variables (and the state pattern eventually) seems too much.  Either go with the AF plus simple support loops as needed, or develop your "QSM" design to include dynamic launching and inter-process communication.

Message 6 of 9
(4,609 Views)

That is a good point, and my goal.  I didn’t initially realize that the Actor Framework was a QSM (even though it is stated in the documentation).  At that point, I seemed too far in to make any changes.  I plan to break up my QMH into methods to allow for the correct use of the framework.

0 Kudos
Message 7 of 9
(4,609 Views)

In looking at this part of your question:

In the meantime, I plan to have an IOServer that takes the Global Variables and reads and writes their values to/from the Registers.  Are there any hints on how to do this right?

Here is once approach that has worked for me...

First off, my systems are essentially SCADA systems with the primary data source a PLC.  Data points on the PLC can be read and written to but in our environment, changes to data points occuring on the PLC do not cause any notification in LabVIEW.  Hence we must poll.

I have one or more Actors responsible for reading and writing data that is shared with the PLC.  Often it is convenient to use data bound globals to connect to the PLC. This Actor sounds analogous to your idea of an IOServer though your data might be changing from internal sources or perhaps an alternate external source. 

The IOServer has to poll the 'global' since it can be updated by the external hardware without any notification.  The poll is initiated by a 'PerformPoll' message that was issued via a Time-Delayed Send Message.vi call.  This is parth of AF but not in the library proper (look in the vil.lib/Actor Framework folder).  Make sure the PerformPoll.Do method can complete faster than the 'Milliseconds To Wait'  supplied to the TDSM or the IOServer queue will back up.  Previous values are always retained and compared to avoid sending repeat values to interested Actors (ie subscribers).

Actors interested in one or more values subscribes by providing delivery information (perhaps a self addressed 'ValueUpdate' message) to the IOServer.  If the values are noisy (change a lot) the subscriber can also indicate a minimum delta and/or minimum time before the ValueUpdate message is sent.

With the exception of the external hardware, the IOServer is solely responsible for updating these values.  Actors can send a ChangeValue message to the IOServer.  Without polling, the IOServer can also look at other Actors that subscribed to this data point and send them the ValueUpdate message.

Regarding AQ's three points in his message above... Though we have not eliminated polling, by isolating where the polling occurs and then using the ValueUpdate message the other actors can stay idle until a change occurs.  It does not eliminate creating a data copy of the global during the poll, but does minimize it by not having multiple actors responsible for reading the same global. While technically we only have one reader, subscribers are indirectly reading the value and there is no specified order that they will be notified. This is OK for my systems but you might need to consider alternate broadcast techniques.

As an aside, it is possible to do this without the use of globals, but I am not sure it eliminates the data copy.  My data points are small (boolean, double int32) so I do not really concern myself with the copy.  I also do not poll data points that do not have a subscriber.

I hope this helps.

Kurt

0 Kudos
Message 8 of 9
(4,609 Views)

Kurt Grice wrote:

The IOServer has to poll the 'global' since it can be updated by the external hardware without any notification.  The poll is initiated by a 'PerformPoll' message that was issued via a Time-Delayed Send Message.vi call.  This is parth of AF but not in the library proper (look in the vil.lib/Actor Framework folder). 

The Time Delayed Message is on the Actor Framework >> Advanced palette.

0 Kudos
Message 9 of 9
(4,609 Views)