Nov
04

Worker pool – a design pattern for parallel task execution in LabVIEW

by Tomi Maila, Nov 4, 2009 at 2:00 pm
1 Star2 Stars3 Stars4 Stars5 Stars (6 votes, average: 5.00 out of 5)
Loading ... Loading ...

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.

Dataflow worker pool

Dataflow worker pool block diagram

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.

Queue based worker pool block diagram

Queue based worker pool block diagram

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

224.12 KB
You must be logged in to download this item!
Please login or register now.

Print This Post Print This Post

9 Comments

Make A Comment
  • Jeffrey Habets Said:

    Hi Tomi,

    This is a very interesting implementation of an execution engine for the command design pattern. Thanks!

    Jeffrey

  • 2mzu Said:

    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.

  • – Unlimited parallelism & concurrency with recursive dataflowExpressionFlow Said:

    [...] Worker pool – a design pattern for parallel task execution in LabVIEW [...]

  • Tomi Maila Said:

    I am pleased to make you happy :) I added an additional example implementation to the example code that reuses non-busy workers.

  • Todd Said:

    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.

  • eiskalterengel Said:

    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

  • Tomi Maila Said:

    The downloads should now work, sorry for the delay.

  • ahoenselaar Said:

    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!

  • Tomi Maila Said:

    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.

Comments RSS Feed   TrackBack URL

Leave a comment

You must be logged in to post a comment.

Get Adobe Flash playerPlugin by wpburn.com wordpress themes