DEV Community

Yasir Jafri
Yasir Jafri

Posted on

Concurrency Patterns: Balking Pattern

Introduction

The Balking Design Pattern is a behavioral design pattern used to manage state-dependent actions in a system. It ensures that operations are executed only when the system is in an appropriate state. If the required precondition is not met, the operation is aborted or the system "balks". For those like me, who don't know what Balking is, this is what google has to say about it: "hesitate or be unwilling to accept an idea or undertaking". This pattern is particularly useful in multithreaded environments or systems where invalid actions could cause conflicts or errors.

Balking pattern is also considered more of an anti-pattern than a design pattern by some people in the community. If an object cannot support its API, it should either limit the API so that the offending call is not available, or so that the call can be made without limitation. This is an old pattern which seems to have arisen when JVMs were slower and synchronization wasn't as well understood and implemented as it is today. Regardless it is worth discussing and whether to use it or not is upto the developers.

The Balking Pattern relies on three fundamental concepts

  1. Guard Condition: A condition that must be satisfied for an operation to proceed.
  2. State-Dependent Actions: Operations that depend on the current state of the system.
  3. Thread Safety: The pattern often uses locks or other synchronization mechanisms to ensure safety in concurrent environments.

Let's understand these with an example:

A printing system demonstrates the Balking Pattern:

  • Scenario: A printer can only process one print request at a time. Even though multiple processes can place the print request.
  • Guard Condition: The printing must not be actively "printing" to handle a new print request.
  • Behavior: If the printer is busy, the system balks and does not proceed with the new print requests.

Note: Yeah, we can handle this using a queue, but let's assume for now we don't know that such an elegant data structure exists.

import threading
import time

class Printer:
    def __init__(self):
        self.state = "idle"
        self.lock = threading.Lock()

    def start_printing(self, job_id):
        print(f"Attempting to start Print Job {job_id}...")

        with self.lock:  # Ensure thread safety
            if self.state == "printing":
                print(f"Balking: Print Job {job_id} cannot start. Printer is busy.")
                return
            self.state = "printing"

        # Simulate the printing process
        print(f"Print Job {job_id} started.")
        time.sleep(3)
        print(f"Print Job {job_id} completed.")

        with self.lock:
            self.printing = "idle"

# Multiple threads attempting to start print jobs
printer = Printer()

threads = [
    threading.Thread(target=printer.start_printing, args=(1,)),
    threading.Thread(target=printer.start_printing, args=(2,))
]

for t in threads:
    t.start()

for t in threads:
    t.join()
Enter fullscreen mode Exit fullscreen mode

Looking at the code we can see that if we send a print request start_printing to the printer and the printer is busy it will check it's current state self.state and if the state is "printing", it will return without doing anything. Otherwise, it will take up that request and adjust its state accordingly.

When to Use the Balking Pattern

  1. Multithreaded Systems: To prevent race conditions or invalid operations.
  2. State-Dependent Workflows: When actions are permissible only in certain states.
  3. Resource Management: To guard against improper use of shared resources. Objects that use this pattern are generally only in a state that is prone to balking temporarily but for an unknown amount of time. If objects are to remain in a state which is prone to balking for a known, finite period of time, then the guarded suspension pattern may be preferred.

Advantages of the Balking Pattern

  1. Prevents Invalid Operations: Guards ensure operations occur only under valid conditions.
  2. Thread Safety: Particularly useful in multithreaded systems.
  3. Simplifies Logic: Encapsulates state-dependent actions into a clear, reusable pattern.

Disadvantages

  1. Limited Applicability: Most useful when actions are binary (allowed or not allowed).
  2. Potential Overhead: Guard checks and synchronization mechanisms can introduce performance costs.

Conclusion

The Balking Design Pattern provides an effective way to manage state-dependent actions and prevent invalid operations in software systems. By introducing clear guard conditions and ensuring thread safety, it enhances the reliability and maintainability of the system. Whether it's preventing multiple trips in a cab booking system or managing concurrent print jobs, the Balking Pattern offers a structured approach to avoid conflicts and maintain operational integrity. Ultimately, the choice to use the Balking Pattern depends on the specific requirements of your application and its concurrency needs.

References

Top comments (0)