Application Design Patterns: Master/Slave

Updated Jan 5, 2022

Environment

Software

  • LabVIEW

The Master/Slave design pattern is another fundamental architecture LabVIEW developers use. It is used when you have two or more processes that need to run simultaneously and continuously but at different rates. If these processes run in a single loop, severe timing issues can occur. These timing issues occur when one part of the loop takes longer to execute than expected. If this happens, the remaining sections in the loop get delayed. The Master/Slave pattern consists of multiple parallel loops. Each of the loops may execute tasks at different rates. Of these parallel loops, one loop acts as the master and the others act as slaves. The master loop controls all of the slave loops, and communicates with them using messaging architectures.

The Master/Slave pattern is most commonly used when responding to user interface controls while collecting data simultaneously. Suppose you want to write an application that measures and logs a slowly changing voltage once every five seconds. It acquires a waveform from a transmission line and displays it on a graph every 100ms, and also provides a user interface that allows the user to change parameters for each acquisition.
The Master/Slave design pattern is well suited for this application. In this application, the master loop will contain the user interface. The voltage acquisition and logging will happen in one slave loop, while the transmission line acquisition and graphing will happen in another.

Applications that involve control also benefit from the use of Master/Slave design patterns. An example is a user’s interaction with a free motion robotic arm. This type of application is extremely control oriented because of the physical damage to the arm or surroundings that might occur if control is mishandled. For instance, if the user instructs the arm to stop its downward motion, but the program is occupied with the arm swivel control, the robotic arm might collide with the support platform. This situation can be avoided by applying the Master/Slave design pattern to the application. In this case, the user interface will be handled by the master loop, and every controllable section of the robotic arm will have its own slave loop. Using this method, each controllable section of the arm will have its own loop, and therefore, its own piece of processing time.
This will allow for more responsive control over the robotic arm via the user interface.

 

Why Use Master/Slave?

The Master/Slave design pattern is very advantageous when creating multi-task applications. It gives you a more modular approach to application development because of its multi-loop functionality, but most importantly, it gives you more control of your application’s time management. In LabVIEW, each parallel loop is treated as a separate task or thread. A thread is defined as a part of a program that can execute independently of other parts. If you have an application that doesn’t use separate threads, that application is interpreted by the system as one thread. When you split your application up into multiple threads, they each share processing time equally with each other.

This gives you more control on how your application is timed, and gives the user more control over your application. The parallel nature of LabVIEW lends itself towards the implementation of the Master/Slave design pattern.

For data acquisition example above, we could have conceivably put both the voltage measurement and the waveform acquisition together in one loop, and only perform the voltage measurement on every 50th iteration of the loop. However, the voltage measurement and logging the data to disk may take longer to complete than the single acquisition and display of the waveform. If this is the case, then the next iteration of the waveform acquisition will be delayed, since it cannot begin before all of the code in the previous iteration completes. Additionally, this architecture would make it difficult to change the rate at which waveforms were acquired without changing the rate of logging the voltage to disk.

The standard Master/Slave design pattern approach for this application would be to put the acquisition processes into two separate loops (slave loops), both driven by a master loop that polls the user interface (UI) controls to see if the parameters have been changed. To communicate with the slave loops, the master loop writes to local variables. This will ensure that each acquisition process will not affect the other, and that any delays caused by the user interface (for example, bringing up a dialog) will not delay any iteration of the acquisition processes.

Build a Master/Slave Application

The Master/Slave design pattern consists of multiple parallel loops. The loop that controls all the others is the master and the remaining loops are slaves. One master loop always drives one or more slave loops. Since data communication directly between these loops breaks data flow, it must be done by writing and reading to messaging architectures i.e. local or global variables, occurrences, notifiers, or queues in LabVIEW. Figure 1 shows how different loops are connected to each other using shared data.

Figure 1: Master/Slave Overview

Example - Synchronizing Loops

This application has the following requirements:

  • Create a Queued Message Handler to handle the user interface. The user interface should contain two toggle process buttons with LEDs, and an exit button.

  • Create two separate processes that both switch an individual LED on and off at different rates (100 and 200 ms intervals). These two processes will be controlled using the user interface.


Our first step will be to decide which process will be the master and which processes will be the slaves. In this example, the user interface will be placed inside the master loop, and the two blinking LED processes will be the two slave loops. The user interface will control the operation of each slave loop with local variables.
 

Messaging architectures (shared data)

Problem: If multiple loops try to write data to the shared variable at the same time, there is no way to tell which value may ultimately get written. This is known as a race condition.

Solution: Place an “acquire/release semaphore” pair around any piece of code that writes to the global. This ensures that multiple loops don’t attempt to write to the global at the same time. Among the examples included with LabVIEW are a couple that demonstrate the use of semaphores. Semaphores will lock the global data while being written to in order to avoid a race condition.
 

Synchronization

Problem: Since the Master/Slave design pattern is not based on synchronization, it is possible that the slave loops begin execution before the master loop. Therefore initializing the master loop before the slave loops begin execution can be a problem.

Solution: Occurrences can be used to solve these kinds of synchronization problems.


To find examples that demonstrate the use of occurrences consult the LabVIEW shipping examples. Figure 2 illustrates an example of how to use occurrences.


Figure 2: Occurrence Example