The producer-consumer design pattern certainly is one of the most important design patterns in LabVIEW. I'd like to discuss some advanced concepts when using this architecture.
Shown is the producer consumer architecture using queues (this is a flawed implementation showing only the most important aspects). In the top loop the data (for example acquired from a DAQ device) is enqueued. In the bottom loop the data is dequeued and further processed. To shut down the consumer loop, the queue is released resulting in an error.
There are several varieties of this basic design:
If using the design above, the consumer will process a default element in the last iteration. Correctly instead of the sequence frame, the consumer code should be placed in the 'no error' case of a case structure. (see the last snippet)
As with any parallel process, it is necessary to get the synchronization right. When using the Merge Error.vi (or the new LV2010 prim), only the first error of the input terminals is propagated. But this isn't necessary the first error that occurred. There is almost a 50% chance that the first error lead to invalid data that causes the other vi to throw the error that is finally displayed or logged. The vi of the picture will display error -20003 from the Mean.vi instead of the error 5000 from the DAQ code.
The second, even worse scenario is that one loop keeps spinning and the user isn't aware of the error at all, because the Merge Error.vi still waits for the 2nd terminal to receive data.
There are two error codes generally thrown by the enqueue and dequeue prims: 1 and 1122. 1122 is thrown if the queue is released while the node was waiting for the data (very common in the consumer loop), error 1 is thrown when the queue prim receives an already released queue ref (which can happen if the consumer code takes some time to execute). The problem with error code 1 is, that it is thrown by quite a lot of other nodes as well, so it isn't easy to determine if this is just the signal for the consumer loop to shut down or if it was an error from the consumer code.
I've made a simple vi which converts the error code 1 to 1122 coming from the queue prim.
It also adds a note via the <Append> tag that this was originally code 1. If the error was already present before the queue prim, this is passed instead without the 1->1122 conversion.
The error 1122 is always cleared after the loops, as this is the expected signal to terminate.
Simple enough, each loop needs to stop if the error wire contains an error. The Release Queue prim also operates if an error comes in, so this causes the consumer loop to stop in case of an error in the producer loop.
In the case of an error in the consumer loop, I first clear the expected error 1122. If any other error was thrown, I release the queue in the consumer process to stop the producer. As this only throws error 1 or 1122, it gets cleared before the merge error, so the error of the consumer loop gets propagated to the general error handler.
If the producer loop releases the queue directly after enqueuing the last element, there is a chance that the consumer loop can't dequeue it when it is still busy processing older data. Hence I poll the '#elements in queue' with the Get Queue Status prim.
If an error has occurred in the consumer loop, the release of the queue has thrown error 1122 in the producer process. This would be enough to stop the polling loop, as the default value for '#elements in queue' is 0. But correctly implemented the SubVI that stops the loop is checking the error wire.
Attached is the code from LV 7.1., runs fine in LV2010.
Thanks Felix for the informative nugget.
What I typically do with producer-consumer design pattern with a queue, is to include the run status of the producer loop as well as the error status in the consumer loop.
That means that the consumer loop can stop as signalled by the producer loop.
Although I have not done this yet, you could implement a notification if stop or exit is chosen, so that the producer loop does not kill the queue before the consumer loop has had a chance to process the last data item (or at least extract it from the queue).
I just spotted an example where people did use producer consumer design with some of the code I wrote above. But this did not reflect the purpose of consumer producer design pattern, but was full of local variables to communicated between the two loops.
My intention of this nugget was to cover some advanced issues with this architecture, not the basic use! Please search around in the forums and the NI tutorials to fully get the basics first!
The intention of the queue is to get all data that is produced in the producer loop down to the consumer loop in a 'bunch/batch/all at once' manner for each iteration.
Please digest this thread when you still want to use locals to communicate between the loops, what nightmares you might get and so forth.
Do not use locals for communicating between parallel loops.