Worker pool – a design pattern for parallel task execution in LabVIEW
Creating parallel code in LabVIEW is simple; simply place parallel tasks parallel on VI block diagram and the tasks are executed in parallel. However this design doesn’t bend to use cases when the number of parallel tasks is not known at development time but needs to be specified at runtime. In this post I present simple design pattern for parallel code execution based on recursion. In my next post I study the recursive concurrency in more detail.
Let’s consider first the dilemma to which this design pattern is a solution. You want to execute a number of different tasks in parallel. The tasks that need to be executed are not known at development time. Alternatively the tasks are known at the development time but the required level of parallelism is not known. As an example, consider a complicated data analysis task that can be massively parallelized. However, you may want to optimize the parallelization at runtime to meet the number of processors availble in the workstation. As an other example consider a server serving a number of network connections. Each connection is represented by a listener task on your server application. You can only know the number of listeners requires at runtime.
A common design to execute parallel tasks at runtime is to first open a VI reference using application server, then prepare the VI for execution and finally use the VI server Run VI method to execute the VI. As there is not dataflow dependency between the parallelly executing VI and the calling code, passing the results and possible errors from the executing code requires external mechanisms.
The worker pool design pattern I am introducing in this post is based on VI recursion and command design pattern. I present two variations of the design pattern, a dataflow based worker pool and queue based worker pool. For both variations of the pattern, the tasks are represented by a command design pattern. That is, each type of a task is represented by a class that is a child (descendant) class of Command class. Each of these classes implement a execute method of the command class that actually executes the task. A typical task class contains 1) create method for initializing the task parameters, 2) an execute method for performing the task and 3) methods for querying the results of the executed task. The initialized commands are passed for the worker pool, that then executed the execute method of each task.
The dataflow based worker pool is suitable for use cases where the tasks can be completely specified before parallel execution is required and the results of the executions are not required until all parallel tasks have finished. The idea is very simple, pass an array of Command objects to a worker pool VI. The worker pool VI then removes one task from the array and executes it. In parallel to this, worker pool calls to itself recursively to execute the rest of the tasks. The worker pool VI needs to be re-entrant of type share clones between instances. All the tasks in the array are this way executed in parallel and there are no development time limitations to the number of workers that can execute in parallel. The results of the tasks are returned in dataflow manner. The image below illustrates an implementation of dataflow worker pool pattern.
The queue-based worker pool is suitable for use cases where the tasks need to be created dynamically during the execution of the application and where the results of the tasks need to be processed as soon as the tasks have finished executing. The queue-based worker pool pattern combines the producer-consumer design pattern with the command design pattern. The idea is similar to the dataflow worker pool. However unlike in the dataflow worker pool, a queue of commands and a queue for results are passed for the worker pool. The worker pool reads a command from the commands queue, executes it and writes the result to the results queue. In parallel to this, worker pool executes itself recursively, as in dataflow worker pool pattern. The commands are enqueued to the command queue and the results are dequeued from the result queue in some parallel processes. When the command queue is closed, the worker pool finishes executing the tasks in progress and returns.
There can be multiple variations of these base patterns. The tasks can be passed via command queue but the results returned via dataflow output terminal. A worker pool can combine a sequential loop based task execution with presented recursive parallel execution to allow reusing workers that are currently idle. It would be a good design to encapsulate the worker pool to a class that implements general worker pool interface.
I have attached a simple example project illustrating the usage of the presented design patterns. Please sign-up to ExpressionFlow to download the project. The example project requires LabVIEW 2009 development environment.
Worker Pool.zip 1.1
Please login or register now.
9 Comments
Make A CommentComments RSS Feed TrackBack URL
Leave a comment
You must be logged in to post a comment.




November 6th, 2009 at 4:33 pm
Hi Tomi,
This is a very interesting implementation of an execution engine for the command design pattern. Thanks!
Jeffrey
November 9th, 2009 at 4:50 pm
Very elegant, unlike the (obvious) VI server solution (wrapping up Run VI invoke node in OO) it runs with runtime, even with mobile module with 0 modification. Thanks.
November 10th, 2009 at 4:09 pm
[...] Worker pool – a design pattern for parallel task execution in LabVIEW [...]
November 10th, 2009 at 6:24 pm
I am pleased to make you happy
I added an additional example implementation to the example code that reuses non-busy workers.
December 19th, 2009 at 9:19 pm
This is great stuff! Do you mind posting it on the NI Community (ni.com/community)? I want to link to it from the news feed on the LabVIEW splash screen. Send me an email when it’s up and I’ll push it live.
June 7th, 2010 at 7:38 am
Hi all,
I already signed-up to ExpressionFlow to download this project, but I didn’t find any possibilty to download this project.
Could anybody help me out?
Thanks very much
Bets regards
June 19th, 2010 at 12:59 am
The downloads should now work, sorry for the delay.
July 1st, 2010 at 6:18 am
It seems like the final recursion depth is identical to the number of commands to be executed. The recursion depth in LabView is limited to 15,000 / 35,000 (32 bit / 64 bit versions), which will also limit the maximum number of jobs that can be processed.
Obviously, there are many trivial workarounds, but the generality of this design pattern strikes me as limited.
Still an interesting approach!
July 7th, 2010 at 10:34 pm
Ahoenselaar, thanks for the insight. In general I would not like to run more than 10000 concurrently executing tasks on any single computer system, irregardless if there is a hardware limitation or not. That thing said, it would be easy to work around the recursion limitation. If you execute m > 1 parallel commands on each recursion level instead of one, you will have order of m^n concurrent tasks at recursion level n, when n >> 0. So practically you reach more concurrent tasks already at recursion level of order 100 than any present computer can handle.