Jump to content

QSM (Producer/Consumer) Template


Recommended Posts

Hi everyone. I had this posted on NI Forums few days ago: http://forums.ni.com...te/td-p/2151376 and there were no interests as of yet. So I thought I'll post this here.

Attached is a template I decided to do with QSM Producer Consumer style before I start with a project.

I would appreciate any comments/criticism (hopefully related to this :))/improvements that you might be able to do in this template.

Thanks

Kas

QSM Template.zip

Link to comment

I would appreciate any comments/criticism (hopefully related to this :))/improvements that you might be able to do in this template.

Umm... what kind of comments/criticism are you looking for? 'Cause I could (and on sometimes do, much to the annoyance of fellow forummers) go on for days talking about the problems of QSM designs. If the template fits your needs, go ahead and give it a test drive on this project. Chances are you'll discover things you want to change for the next project. Make the changes, rinse, and repeat.

Aside from the inherent QSM problems, there were a couple things that jumped out at me.

-First, why do you have separate sets of enqueue and dequeue vis for each queue? Other than the icons (which I think are interesting) they are exactly the same.

-Second, why are you using a functional global for data storage instead of passing data on the queues?

-Third, I know there is a long history of calling each case a "state," but the terminology is incorrect. I've seen hints that people inside NI are beginning to change how they refer to them. What you have is not a pair of state machines, they are message handling loops. You'll be better off in the long run if you change how you think about them.

-Fourth, you have two event processors in the top loop: the dequeue processes incoming messages and the event structure processes user inputs. Doing that in a multithreaded application is asking for trouble. And since you don't have any timeouts enabled for either the dequeue or the event structure, you'll run into that trouble sooner rather than later.

-Finally, the template may be quite sufficient for smallish applications, but expect to run into race conditions and other problems if it grows much--especially if you attempt to add additional loops.

Part of the reason the JKI SM works is because it is only a single loop. The strategies simply don't transfer well (imo) to a multithreaded application.

Link to comment

My biggest suggestion is to use the JKI template as is. It’s a tested and well-thought-out design. If you need a second process, you can use another JKI SM or a simpler queue-run process.

As to your current design:

1) as Daklu mentioned, don’t have two different receiving mechanisms in the same loop; use a User Event to communicate through the event structure.

2) A subtler flaw is that the loops use the same queue for internal operations and for receiving messages from external processes. This can lead to race-condition bugs as various processes queue up sets of messages at the same time. The JKI template uses a separate queue (actually a string) for internal operations.

— James

Oh, and instead of the functional global, consider using a “Message” system like Daklu’s Lapdog package for getting data between loops.

Link to comment

Thanks to both of you for your replies.

1) For passing data between loops, I found it difficult to implement the normal way (a cluster of string (for case command/messages) and variant (for data)) because I have multiline string that turn into queues i.e. each line would then represent a queue (shown in "Macro:initialise" case), so I thought using functional globasl instead.

2) Both loops have seperate queue names, and I wire both queues to both loops when I want to refer to both of them i.e. from producer to consumer with a button press or from consumer to producer based on hardware replies (depending on the hardware reply, I might have to enable/disable blocks of clusters or even a whole wizard (which I implemented on FP)). Even if both "case structures/message handlers" might have the same name, because I refer to them through seperate queues, I thought it might work.

3) I have seperate sets of enqueue and dequeue because I was worried about race conditions between the two loops. There is a slight difference between the two dequeu's however. In the first loop I have an event structure, and the dequeue for that loop never halts the queue (i.e. waiting for a queue to be present), but as soon as the queue is empty, I feed an empty string, forcing the software to halt on the "Idle" case (i.e. where the event structure is), the second dequeue however doesnt have this, and so the loop is halted on the dequeue.

4) I didn't think it would be a bad idea to have 2 event processors in the first loop. I thought I'll use the first loop to handle the FP buttons, menus etc. and use the second loop as the main workhorse in machine processing in parallel. I thought that if I introduce the third loop for FP activity might be more work for PC.

The above answers shows the state of thinking I currently have about various programming structures. I gues my lack of knowledge in "good and bad" programming techniques is starting to show up. So far, I've only used state machines "JKI mainly", but as with everything, the time is come for me to outgrow (mainly because I have no choice :D) this and go forth with something better.

drjdpowell

you mentioned "Daklu’s Lapdog package". do you have a link that I have a look at. Maybe I can incorporate that here.

Thanks to both of you, for me, this is the easiest way to learn things.

Kas

Link to comment

1) For passing data between loops, I found it difficult to implement the normal way... so I thought using functional globasl instead.

If you have to use functional globals because your messaging system can't handle data, I'd look at changing your messaging system. :) That's a pretty stiff restriction to put on your designs.

2) Both loops have seperate queue names, and I wire both queues to both loops... I thought it might work.

This is actually correct... you *want* loops to be able to send messages to each other. The queues don't require different names either. (I believe it is preferable to not use named queues at all.)

3) I have seperate sets of enqueue and dequeue because I was worried about race conditions between the two loops.

It's good to worry about race conditions, but having separate sets of enqueue/dequeue vis doesn't affect race conditions one way or the other. Put a timeout terminal on your dequeue vi and you can use a single enqueue/dequeue set instead of having multiple sets.

There is a slight difference between the two dequeu's however.

Ahh... I missed that. In that case, you won't end up with an unresponsive UI, but you will end up with situations where the UI loop is blocked waiting for a FP event and messages are getting backed up on the queue. If you're going to have multiple receiving mechanisms in a single loop they both must have timeouts.

4) I thought that if I introduce the third loop for FP activity might be more work for PC.

No, not in any amount you would notice.

I gues my lack of knowledge in "good and bad" programming techniques is starting to show up.

We've all been there. It'll take time and you'll get lots of bumps and bruises along the way, but if you spend enough time you'll learn the ins and outs.

Yayks, this seems to be LVOOP design. I'm not in to that yet. That should probably be my next step to cross.

I would suggest learning multi-loop programming before diving into LVOOP. LapDog itself is LVOOP, but the principles of multi-loop programming are not restricted to object oriented techniques. If you open up the MessageQueue.Dequeue method and look at the block diagram you see it has some similarity to your dequeue vi. All the other MessageQueue methods are simple wrappers around NI's queue functions.

If you want to start learning how to build robust multi-loop designs, here are three rules of thumb to get you started:

1. Restrict each loop to a single mechanism for receiving information from external sources. (Other loops or the user interface.)

2. Don't have a loop send messages to itself. Use sub vis instead. In fact, I don't even wire the loop's queue into the message handling case structure.

3. Find and remove any requirement a loop has for receiving specific sequences of messages. With a queue the receiving loop has no way of knowing what the next message will be, and neither do you. It could be anything. Your loops should be able to correctly handle any messages at any time, without becoming unpredictable. Keep that in mind when you're writing code. Yes, that means you should forget about macros--they cause race conditions. (Except under very specific circumstances.)

Link to comment

Daklu, this is exactly what I was looking for, and then some :lol:. As you sugested, I'm going to wait and go through some projects with multi loop programming before I dive into LVOOP (I keep getting scared from it, people in the forum make it sound alittle hard, todd's posts included :P , but nevertheless a necassary step).

One thing I'm not sure of, when you said "Put a timeout terminal on your dequeue vi", would you be able to post an image on how you would do this?

Again, to everyone taking their time on this "DAKLU" especially BIG thank you.

P.S. "Learning LabFu Daily" nice :thumbup1:.

Link to comment

One thing I'm not sure of, when you said "Put a timeout terminal on your dequeue vi", would you be able to post an image on how you would do this?

Sorry, I forget that experienced LV developers have lingo that newer users may not be familiar with. I meant to connect the Timeout input on NI's Dequeue function to a front panel control on your Parse States vi.

post-7603-0-14167600-1347496451.png

That way you can change the Timeout value on each when you create copies of the template without having to mess around inside the the Parse States vi.

post-7603-0-69065600-1347496689.png

I keep getting scared from it, people in the forum make it sound alittle hard...

It depends on what you're trying to do. It's not hard to use a few objects in your applications, yes, creating a well-designed fully object oriented application isn't something you'll know how to do after a couple weeks of poking around... or even a couple months. There's a lot of stuff to learn. Of course, learning how to create a well-designed non-oo application is going to take time too.

BTW, you know those three rules of thumb I gave you to get started on multi-loop programming? They are not generally accepted by the larger LV community. You might find yourself having to defend your decision to follow them if you post code or questions online. We who distrust QSM designs are still in the minority. Just giving you a heads up...

Link to comment

BTW, you know those three rules of thumb I gave you to get started on multi-loop programming? They are not generally accepted by the larger LV community. You might find yourself having to defend your decision to follow them if you post code or questions online. We who distrust QSM designs are still in the minority. Just giving you a heads up...

Haha.. I had no idea people are taking sides on this. I feel like part of a rebelion.

Seriously though, thanks a bunch. My gratitude comes from UK.

Regards

Kas

Link to comment

BTW, you know those three rules of thumb I gave you to get started on multi-loop programming? They are not generally accepted by the larger LV community. You might find yourself having to defend your decision to follow them if you post code or questions online. We who distrust QSM designs are still in the minority. Just giving you a heads up...

The JKI template follows (or at least allows one to follow) those three good rules. It has one message receiving system and has no need to send messages to itself nor have messages that must be executed in a specific order. It does this by keeping a separate, internal-only, by-value queue for actual operations (actually a string). This makes it a lot better than many/most “QSM” designs.

Link to comment

This makes it a lot better than many/most “QSM” designs.

I agree it is an improvement over most QSM implementations. The JKI SM is a single loop QSM whereas traditional implementations are multi loop constructs. I have mentioned in the past my criticisms apply to multi loop QSMs, but I'm not always very clear about it. There are a few things I don't like about JKI's SM, but they are soft objections having to do with style and clarity rather than hard objections due to inherent technical flaws (as is the case with multi-loop QSMs.)

For instance, I'm not at all fond of the practice of using cases as a substitute for sub vis. I think using a queue to control execution flow unnecessarily obfuscates the code and is more error prone compared to simply connecting sub vis on a block diagram. Complex branching can get especially tricky using that technique. You are correct that the JKI SM only has one external message receiving system, but the private queue is in effect an internal message receiving system. Yes, keeping the queue private does eliminate the most egregious flaw of multi loop QSMs. It also encourages developers to avoid creating sub vis and lends itself to "sequential message" thinking. These habits create problems for developers moving from simple data collection and recording tools into applications with interactive UIs.

Another thing I don't like is using user events for the primary communication transport between loops. Events are... quirky. Every so often a thread springs up with somebody having a weird issue with events not behaving how they expect. (Somewhere in the back of my head I have the notion that multiple event structures on a single block diagram is dangerous, but the memory is vague and could be wrong.) Events favor broadcast or observer-based systems and I've described on other threads why I don't like those. Queues are far more transparent, predictable, controllable, and just easier to work with.

I would consider the JKI SM for things like a dialog box in non-LVOOP code. Michael's 3-Button dialog rewrite is a good example. It's a relatively simple UI element that may contain more functionality than can comfortably fit on a block diagram, doesn't need real time communication with other code, and for portability shouldn't call any sub vis. Beyond that I think other techniques provide more flexibility and better readability.

Link to comment

Complex branching can get especially tricky using that technique.

I can attest to that. A 3 machine control project ended up being so big that took me a week to decipher the flow of excecution (i.e. which case is going where). It can look much cleaner however if the embeded "separator no-op" frames are used properly. JKI SM does tend to be a pretty generalised solution for machine controlling and data acuisition aplications (for small to "maybe" medium size projects), and thats as far as it goes I think.

On another note, Daklu, I see on your second image you've wires the 1 ms timout to the event structure. wouldn't that mean that the event structure will be executed every 1 ms? From my experience, the whole point on using an event structure in an SM or even multiloop programs is to halt the PC work (so as to not overwhelm it) and act purely as the main controller to the other loops. If there is nothing to be done, the event structure halts and wait for an event (i.e. button press).

Kas

Edited by Kas
Link to comment

For instance, I'm not at all fond of the practice of using cases as a substitute for sub vis.

I mostly use subVIs for lower-level operations, with cases for the high-level operations. At the high level I don’t find any advantage to subVIs; a “macro” of JKI commands just becomes a long chain of subVIs. The cases have no reusability like a subVI, but the higher level is application-specific so that doesn’t matter.

It is perhaps way too easy to carry on using cases down into the low-level operations that really should be subVIs. I did make that mistake at first.

Another thing I don't like is using user events for the primary communication transport between loops. Events are... quirky. Every so often a thread springs up with somebody having a weird issue with events not behaving how they expect. (Somewhere in the back of my head I have the notion that multiple event structures on a single block diagram is dangerous, but the memory is vague and could be wrong.) Events favor broadcast or observer-based systems and I've described on other threads why I don't like those. Queues are far more transparent, predictable, controllable, and just easier to work with.

I’m not sure Events are actually quirky, rather than just people mistakenly using the same event registration in more than one event structure.

I use the JKI and Events with VIs that have a UI. I tend to use queues for non-UI loops (I assume they have less overhead). And I tend not to use Events as broadcast or one-to-many communication methods.

Link to comment

It is perhaps way too easy to carry on using cases down into the low-level operations that really should be subVIs. I did make that mistake at first.

I find that if I use a case from more than three other cases, it is doomed to become a subVI instead. The only annoying part is that I tend to pass in my clustersaurus into it, which ruins the front panel.

Link to comment

Another thing I don't like is using user events for the primary communication transport between loops. Events are... quirky. Every so often a thread springs up with somebody having a weird issue with events not behaving how they expect.

I'm not a fan of the JKI thingy either, but I would argue that classes are far more prone to this than events (or anything else for that matter).

The topology I've now adopted for inter-process comms is a queue for the control, and events for response. This gives a Many-To-One for control (great for controlling via UI and TCPIP) and a One-to-Many for the response. A queue for the response has inherent problems with leftover messages and difficulties with permeating the messages to other processes. However, with Events you can easily add monitors to the response and dynamically register other subsytems to the messages (errors for example).

Edited by ShaunR
Link to comment

On another note, Daklu, I see on your second image you've wires the 1 ms timout to the event structure. wouldn't that mean that the event structure will be executed every 1 ms? From my experience, the whole point on using an event structure in an SM or even multiloop programs is to halt the PC work (so as to not overwhelm it) and act purely as the main controller to the other loops. If there is nothing to be done, the event structure halts and wait for an event (i.e. button press).

That's correct. However, you have two message receiving mechanisms in that loop: the queue and the event structure. You have to make sure both are being serviced. If you don't have a timeout on the event structure none of the messages from the lower loop will be processed until the user initiates some action on the UI.

I've attached a simple Reactor Controller project based on your template where the event structure does not have a timeout. Use the up/down buttons to change the temperature setpoint and watch what the UI reports as the current temperature compared to what the debug indicator shows is the current temperature. Then take the temp up to 163 degrees and watch what happens. After you do that, change the event structure timeout to 1 ms and do the same thing with the temp setpoint. See how the UI is able to respond to the messages from the controller loop? That's because the timeout on the event structure is allowing the dequeue vi to process messages.

This problem is why I don't think it's a good idea to have multiple message receiving mechanisms in a single loop.

QSM Reactor Controller.zip

I mostly use subVIs for lower-level operations, with cases for the high-level operations. At the high level I don’t find any advantage to subVIs; a “macro” of JKI commands just becomes a long chain of subVIs. The cases have no reusability like a subVI, but the higher level is application-specific so that doesn’t matter.

Sub vis are useful for more than just reuse. They also serve as a way to abstract away the details and using the context help window to read notes about it helps me a lot. When I'm sifting through a chain of sequential cases I find the details are too exposed and I have a harder time seeing the bigger picture. Plus it's way easier to test a single vi in isolation for proper behavior than a single case in a large QSM.

The reactor controller is a perfect example of someplace I'd use sub vis instead of sequences cases. The sequence of 5 cases (SimulateReactor, NormalOperatingModel, InstabilityModel, ReadReactorTemp, and IsTempOverLimit?) that executes every 50 ms should be compressed into a single message handling case with sub vis chained together. It would be far easier to understand what is going on than switching between multiple cases.

I'll admit on rare occasions I do find it more efficient to execute a fixed sequence of case statements instead of putting each one in a sub vi. Here's an example from a recent project I did.

post-7603-0-45180800-1347598111_thumb.pn

In this instance I had to do a lot of front panel updates. Rather than using control references and sub vis I decided to put all that code on the UI block diagram. The difference is I don't make each one a separate case in a string based case structure; I put it into a For loop and interate through them sequentially. It's much clearer to me this way because I can easily see that all these actions occur when the RefreshMonitoringData message is received. Also, my main case structure isn't cluttered up with extra message handlers that shouldn't be available outside of this one message.

It is perhaps way too easy to carry on using cases down into the low-level operations that really should be subVIs. I did make that mistake at first.

So did I. And so do many other developers.

I’m not sure Events are actually quirky, rather than just people mistakenly using the same event registration in more than one event structure.

There's more to it than that. Having two event queues behind the scenes occasionally trips people up. In general, there are far more posts asking "Why don't my events work right?" than there are posts asking "Why don't my queues work right?"

I find that if I use a case from more than three other cases, it is doomed to become a subVI instead. The only annoying part is that I tend to pass in my clustersaurus into it, which ruins the front panel.

You've been around a long time asbo... I thought you had already jumped to oop?

Link to comment

A queue for the response has inherent problems with leftover messages and difficulties with permeating the messages to other processes.

Can you explain what you mean by leftover messages?

The topology I've now adopted for inter-process comms is a queue for the control, and events for response.

So since any given component can receive both control messages and response messages, you implement two message receiving loops in them? Or do you use the "event structure inside the timeout case" technique?

Link to comment

Can you explain what you mean by leftover messages?

Where the dequeue loop stops before the message has been processed so that when it starts up again, it processes the stale message.

So since any given component can receive both control messages and response messages, you implement two message receiving loops in them? Or do you use the "event structure inside the timeout case" technique?

Sometimes 3 (if it uses TCPIP) depending on what you call a "message receive" loop.

There is only 1 "receive" loop (i.e. that dequeues messages and acts upon them), but there maybe an event handler (UIs tend to have these by default) and/or a TCPIP handler. Both of these "receive" incoming messages via their respective channels and place them on the receive queue proper. So there are 3 ways that a message may get to be processed in the most complicated case.

1. By placing a message directly on the receive queue (normally only the sequencer will do this).

2. By sending the message to the TCPIP handler (which then places it on the receive queue)

3. By sending the message as an event (which then places it on the receive queue).

Although all modules can have all three. Usually No.3 is only used for monitoring purposes if there is no UI (since a UI is event driven-it's there already). It is much more common in non-ui subsystems to have no.s 1 and 2 except for, say, an error handler which (will be 1&3).

Since the "receive loops" for TCPIP and events are little more than forwarders (the intention being for within the subsystem only), this means they can be dropped into pretty much any subsytem that doesn't have a UI (well. the TCPIP one can be dropped into anything).

Link to comment

The reactor controller is a perfect example of someplace I'd use sub vis instead of sequences cases. The sequence of 5 cases (SimulateReactor, NormalOperatingModel, InstabilityModel, ReadReactorTemp, and IsTempOverLimit?) that executes every 50 ms should be compressed into a single message handling case with sub vis chained together. It would be far easier to understand what is going on than switching between multiple cases.

I just don’t see it. The 5 subVIs chained on some clustersaurous object doesn’t seem that clearer than a JKI macro:

SimulateReactor

NormalOperatingModel

InstabilityModel

ReadReactorTemp

IsTempOverLimit?

And reordering the sequence or adding/removing actions is very fast with a macro.

I'll admit on rare occasions I do find it more efficient to execute a fixed sequence of case statements instead of putting each one in a sub vi. Here's an example from a recent project I did.

Ah, yes, the ability to connect directly to UI indicators and locals is one of the reasons I like using the JKI cases instead of subVIs for the high-level code that interfaces with the UI. But often times a particular UI update needs to be triggered by more than one action; in your design your updates only happen in one message case.

— James

There's more to it than that. Having two event queues behind the scenes occasionally trips people up. In general, there are far more posts asking "Why don't my events work right?" than there are posts asking "Why don't my queues work right?"

Events are more complicated than a simple queue. But not necessarily more complex than a structure you might use instead. Note that if you have a separate loop for UI events that send messages by queue to your main message handler, then you have the exact same ordering issue in the link you gave above (i.e. you could fire a value change event, then enqueue a message, and the order of arrival at the message handler is indeterminate.

The topology I've now adopted for inter-process comms is a queue for the control, and events for response. This gives a Many-To-One for control (great for controlling via UI and TCPIP) and a One-to-Many for the response. A queue for the response has inherent problems with leftover messages and difficulties with permeating the messages to other processes. However, with Events you can easily add monitors to the response and dynamically register other subsytems to the messages (errors for example).

I use one Queue (or User Event) per receiving process and to get One-to-Many I have the senders maintain an array of the receivers’ Queues. A difference with this from User Events is that instead of the sender needing to pass the User Event to the receiver in order to set things up, the receiver has to get it’s queue reference passed to the sender (in a “registration” message).

Link to comment

Use a class?

You've been around a long time asbo... I thought you had already jumped to oop?

I should have expected that response. ;) More often than not, I don't come in at ground level on a project and OOP was not part of the architecture in most projects I've worked on. I've been applying it as I can with newer projects, though.

Link to comment

Where the dequeue loop stops before the message has been processed so that when it starts up again, it processes the stale message.

Hmm... can you describe a use case where you want to stop and restart a message receiving loop? The only time I ever stop a message loop is when the component is being destroyed. If I need to start the component up again it creates a new message receiving queue.

1. By placing a message directly on the receive queue (normally only the sequencer will do this).

2. By sending the message to the TCPIP handler (which then places it on the receive queue)

3. By sending the message as an event (which then places it on the receive queue).

Okay, that's pretty much the strategy I use too, except only UI components have event loops. It seems like an unnecessary extra step since I don't have to worry about stale messages. On the other hand, it does more clearly separate the component's control/request interface (used by the owning component) from the event handling interface (used by subcomponents.) Maybe I'll try it out and see how I like it...

Link to comment

I feel bad breaking the momentum here, but I thought I'll use this opportunity to learn as much as I can. Attached is the revised template, but I couldn't use just one set of enqueue and dequeue as daklu suggested. It kept throwing an error, so I went back to using two sets. Further more, from what I can see, if queued producer/consumer structure is used, for every consumer loop a separate queue needs to be implemented. If only one queue is used, how would you control which consumer loop should dequeue the message that the producer sent. This is of course provided that more than 2 consumer/message handler loops are used.

Thanks

Kas

QSM Template.zip

Edited by Kas
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.