How to run threads A, B, and C in a sequence?

Medium
12 years ago

How can you execute threads A, B, and C in a specific sequence using multithreading?

Sample Answer

There are several ways to execute threads A, B, and C in a specific sequence using multithreading. Here are a few common approaches:

1. Using join() method

This method involves starting each thread and then using the join() method to wait for each thread to complete before starting the next one.

import threading

def task_a():
    print("Executing Task A")

def task_b():
    print("Executing Task B")

def task_c():
    print("Executing Task C")


thread_a = threading.Thread(target=task_a)
thread_b = threading.Thread(target=task_b)
thread_c = threading.Thread(target=task_c)


thread_a.start()
thread_a.join()  # Wait for thread_a to complete

thread_b.start()
thread_b.join()  # Wait for thread_b to complete

thread_c.start()
thread_c.join()  # Wait for thread_c to complete

print("All tasks completed in sequence.")

Explanation:

  • Each thread is created using threading.Thread() and started with start().
  • The join() method ensures that the main thread waits for the respective thread to finish its execution before proceeding to the next thread.

2. Using Semaphores

Semaphores can be used to control the order of execution. Each thread waits on a semaphore before starting, and signals the next thread when it finishes.

import threading
import time


class SemaphoreController:
    def __init__(self):
        self.sema_a = threading.Semaphore(1)  # Initially unlocked
        self.sema_b = threading.Semaphore(0)  # Initially locked
        self.sema_c = threading.Semaphore(0)  # Initially locked

    def task_a(self):
        with self.sema_a:
            print("Executing Task A")
            time.sleep(1)
            self.sema_b.release()  # Signal to task_b to start

    def task_b(self):
        with self.sema_b:
            print("Executing Task B")
            time.sleep(1)
            self.sema_c.release()  # Signal to task_c to start

    def task_c(self):
        with self.sema_c:
            print("Executing Task C")
            time.sleep(1)


if __name__ == "__main__":
    controller = SemaphoreController()

    thread_a = threading.Thread(target=controller.task_a)
    thread_b = threading.Thread(target=controller.task_b)
    thread_c = threading.Thread(target=controller.task_c)

    thread_a.start()
    thread_b.start()
    thread_c.start()

    thread_a.join()
    thread_b.join()
    thread_c.join()

    print("All tasks completed in sequence.")

Explanation:

  • SemaphoreController manages the semaphores.
  • sema_a is initialized with a value of 1, allowing task_a to start immediately.
  • sema_b and sema_c are initialized with a value of 0, so task_b and task_c wait until they are released.
  • Each task acquires its semaphore using a context manager (with self.sema_x:), executes, and then releases the next semaphore.
  • time.sleep(1) is added to simulate work being done.

3. Using Locks (similar to Semaphores but binary)

Locks can also be used, similar to semaphores, but they are binary (locked/unlocked).

import threading
import time


class LockController:
    def __init__(self):
        self.lock_a = threading.Lock()
        self.lock_b = threading.Lock()
        self.lock_c = threading.Lock()

        self.lock_b.acquire()  # Initially locked
        self.lock_c.acquire()  # Initially locked

    def task_a(self):
        with self.lock_a:
            print("Executing Task A")
            time.sleep(1)
            self.lock_b.release()  # Signal to task_b to start

    def task_b(self):
        with self.lock_b:
            print("Executing Task B")
            time.sleep(1)
            self.lock_c.release()  # Signal to task_c to start

    def task_c(self):
        with self.lock_c:
            print("Executing Task C")
            time.sleep(1)


if __name__ == "__main__":
    controller = LockController()

    thread_a = threading.Thread(target=controller.task_a)
    thread_b = threading.Thread(target=controller.task_b)
    thread_c = threading.Thread(target=controller.task_c)

    thread_a.start()
    thread_b.start()
    thread_c.start()

    thread_a.join()
    thread_b.join()
    thread_c.join()

    print("All tasks completed in sequence.")

Explanation:

  • LockController manages the locks.
  • lock_a is initially unlocked, allowing task_a to start.
  • lock_b and lock_c are initially acquired (locked), so task_b and task_c wait until they are released.
  • Each task acquires its lock using a context manager (with self.lock_x:), executes, and then releases the next lock.
  • time.sleep(1) is added to simulate work being done.

4. Using Condition Objects

Condition objects can also be used to control the sequence of thread execution. Threads wait on a condition and are notified by other threads to proceed.

import threading
import time


class ConditionController:
    def __init__(self):
        self.condition = threading.Condition()
        self.task_order = 0  # 0: A, 1: B, 2: C

    def task_a(self):
        with self.condition:
            while self.task_order != 0:
                self.condition.wait()
            print("Executing Task A")
            time.sleep(1)
            self.task_order = 1
            self.condition.notify_all()

    def task_b(self):
        with self.condition:
            while self.task_order != 1:
                self.condition.wait()
            print("Executing Task B")
            time.sleep(1)
            self.task_order = 2
            self.condition.notify_all()

    def task_c(self):
        with self.condition:
            while self.task_order != 2:
                self.condition.wait()
            print("Executing Task C")
            time.sleep(1)
            self.task_order = 3  # Mark as completed
            self.condition.notify_all()


if __name__ == "__main__":
    controller = ConditionController()

    thread_a = threading.Thread(target=controller.task_a)
    thread_b = threading.Thread(target=controller.task_b)
    thread_c = threading.Thread(target=controller.task_c)

    thread_a.start()
    thread_b.start()
    thread_c.start()

    thread_a.join()
    thread_b.join()
    thread_c.join()

    print("All tasks completed in sequence.")

Explanation:

  • ConditionController manages the condition object and the task order.
  • Each task acquires the condition using a context manager (with self.condition:).
  • The while loop checks if it is the task's turn to execute. If not, it waits.
  • Once it is the task's turn, it executes, updates the task_order, and notifies all waiting threads.
  • time.sleep(1) is added to simulate work being done.

Each of these methods ensures that threads A, B, and C execute in the specified sequence, with thread synchronization mechanisms preventing race conditions and ensuring orderly execution.