Blog

  •  Interrupting a Thread

    Interrupting a thread in Python is a common requirement in multi-threaded programming, where a thread’s execution needs to be terminated under certain conditions. In a multi-threaded program, a task in a new thread, may be required to be stopped. This may be for many reasons, such as − task completion, application shutdown, or other external conditions.

    In Python, interrupting threads can be achieved using threading.Event or by setting a termination flag within the thread itself. These methods allow you to interrupt the threads effectively, ensuring that resources are properly released and threads exit cleanly.

    Thread Interruption using Event Object

    One of the straightforward ways to interrupt a thread is by using the threading.Event class. This class allows one thread to signal to another that a particular event has occurred. Here’s how you can implement thread interruption using threading.Event

    Example

    In this example, we have a MyThread class. Its object starts executing the run() method. The main thread sleeps for a certain period and then sets an event. Till the event is detected, loop in the run() method continues. As soon as the event is detected, the loop terminates.

    from time import sleep
    from threading import Thread
    from threading import Event
    
    classMyThread(Thread):def__init__(self, event):super(MyThread, self).__init__()
    
      self.event = event
    defrun(self):
      i=0whileTrue:
         i+=1print('Child thread running...',i)
         sleep(0.5)if self.event.is_set():breakprint()print('Child Thread Interrupted')
    event = Event() thread1 = MyThread(event) thread1.start() sleep(3)print('Main thread stopping child thread') event.set() thread1.join()

    When you execute this code, it will produce the following output −

    Child thread running... 1
    Child thread running... 2
    Child thread running... 3
    Child thread running... 4
    Child thread running... 5
    Child thread running... 6
    Main thread stopping child thread
    Child Thread Interrupted

    Thread Interruption using a Flag

    Another approach to interrupting threads is by using a flag that the thread checks at regular intervals. This method involves setting a flag attribute in the thread object and regularly checking its value in the thread's execution loop.

    Example

    This example demonstrates how to use a flag to control and stop a running thread in Python multithreaded program.

    import threading
    import time

    def foo():
    t = threading.current_thread()
    while getattr(t, "do_run", True):
    print("working on a task")
    time.sleep(1)
    print("Stopping the Thread after some time.")

    # Create a thread
    t = threading.Thread(target=foo)
    t.start()

    # Allow the thread to run for 5 seconds
    time.sleep(5)

    # Set the termination flag to stop the thread
    t.do_run = False
    When you execute this code, it will produce the following output −

    working on a task
    working on a task
    working on a task
    working on a task
    working on a task
    Stopping the Thread after some time.
  • Thread Deadlock

    A deadlock may be described as a concurrency failure mode. It is a situation in a program where one or more threads wait for a condition that never occurs. As a result, the threads are unable to progress and the program is stuck or frozen and must be terminated manually.

    Deadlock situation may arise in many ways in your concurrent program. Deadlocks are never not developed intentionally, instead, they are in fact a side effect or bug in the code.

    Common causes of thread deadlocks are listed below −

    • A thread that attempts to acquire the same mutex lock twice.
    • Threads that wait on each other (e.g. A waits on B, B waits on A).
    • When a thread that fails to release a resource such as lock, semaphore, condition, event, etc.
    • Threads that acquire mutex locks in different orders (e.g. fail to perform lock ordering).

    How to Avoid Deadlocks in Python Threads

    When multiple threads in a multi-threaded application attempt to access the same resource, such as performing read/write operations on the same file, it can lead to data inconsistency. Therefore, it is important to synchronize concurrent access to resources by using locking mechanisms.

    The Python threading module provides a simple-to-implement locking mechanism to synchronize threads. You can create a new lock object by calling the Lock() class, which initializes the lock in an unlocked state.

    Locking Mechanism with the Lock Object

    An object of the Lock class has two possible states − locked or unlocked, initially in unlocked state when first created. A lock doesn’t belong to any particular thread.

    The Lock class defines acquire() and release() methods.

    The acquire() Method

    The acquire() method of the Lock class changes the lock’s state from unlocked to locked. It returns immediately unless the optional blocking argument is set to True, in which case it waits until the lock is acquired.

    Here is the Syntax of this method −

    Lock.acquire(blocking, timeout)

    Where, 

    • blocking − If set to False, it means do not block. If a call with blocking set to True would block, return False immediately; otherwise, set the lock to locked and return True.
    • timeout − Specifies a timeout period for acquiring the lock.

    The return value of this method is True if the lock is acquired successfully; False if not.

    The release() Method

    When the state is locked, this method in another thread changes it to unlocked. This can be called from any thread, not only the thread which has acquired the lock

    Following is the Syntax of the release() method −

    Lock.release()

    The release() method should only be called in the locked state. If an attempt is made to release an unlocked lock, a RuntimeError will be raised.

    When the lock is locked, reset it to unlocked, and return. If any other threads are blocked waiting for the lock to become unlocked, allow exactly one of them to proceed. There is no return value of this method.

    Example

    In the following program, two threads try to call the synchronized() method. One of them acquires the lock and gains the access while the other waits. When the run() method is completed for the first thread, the lock is released and the synchronized method is available for second thread.

    When both the threads join, the program comes to an end.

    from threading import Thread, Lock
    import time
    
    lock=Lock()
    threads=[]classmyThread(Thread):def__init__(self,name):
    
      Thread.__init__(self)
      self.name=name
    defrun(self):
      lock.acquire()
      synchronized(self.name)
      lock.release()defsynchronized(threadName):print("{} has acquired lock and is running synchronized method".format(threadName))
    counter=5while counter:print('**', end='')
      time.sleep(2)
      counter=counter-1print('\nlock released for', threadName)
    t1=myThread('Thread1') t2=myThread('Thread2') t1.start() threads.append(t1) t2.start() threads.append(t2)for t in threads: t.join()print("end of main thread")

    It will produce the following output −

    Thread1 has acquired lock and is running synchronized method
    **********
    lock released for Thread1
    Thread2 has acquired lock and is running synchronized method
    **********
    lock released for Thread2
    end of main thread
    

    Semaphore Object for Synchronization

    In addition to locks, Python threading module supports semaphores, which offering another synchronization technique. It is one of the oldest synchronization techniques invented by a well-known computer scientist, Edsger W. Dijkstra.

    The basic concept of semaphore is to use an internal counter which is decremented by each acquire() call and incremented by each release() call. The counter can never go below zero; when acquire() finds that it is zero, it blocks, waiting until some other thread calls release().

    The Semaphore class in threading module defines acquire() and release() methods.

    The acquire() Method

    If the internal counter is larger than zero on entry, decrement it by one and return True immediately.

    If the internal counter is zero on entry, block until awoken by a call to release(). Once awoken (and the counter is greater than 0), decrement the counter by 1 and return True. Exactly one thread will be awoken by each call to release(). The order in which threads awake is arbitrary.

    If blocking parameter is set to False, do not block. If a call without an argument would block, return False immediately; otherwise, do the same thing as when called without arguments, and return True.

    The release() Method

    Release a semaphore, incrementing the internal counter by 1. When it was zero on entry and other threads are waiting for it to become larger than zero again, wake up n of those threads.

    Example

    This example demonstrates how to use a Semaphore object in Python to control access to a shared resource among multiple threads, for avoiding deadlock in Python’s multi-threaded program.

    from threading import*import time
    
    # creating thread instance where count = 3
    lock = Semaphore(4)# creating instancedefsynchronized(name):# calling acquire method
       lock.acquire()for n inrange(3):print('Hello! ', end ='')
    
      time.sleep(1)print( name)# calling release method
      lock.release()# creating multiple thread
    thread_1 = Thread(target = synchronized , args =('Thread 1',)) thread_2 = Thread(target = synchronized , args =('Thread 2',)) thread_3 = Thread(target = synchronized , args =('Thread 3',))# calling the threads thread_1.start() thread_2.start() thread_3.start()

    It will produce the following output −

    Hello! Hello! Hello! Thread 1 Hello! Thread 2 Thread 3 Hello! Hello! Thread 1 Hello! Thread 3 Thread 2 Hello! Hello! Thread 1 Thread 3 Thread 2

  • InterThread Communication

    Inter-Thread Communication refers to the process of enabling communication and synchronization between threads within a Python multi-threaded program.

    Generally, threads in Python share the same memory space within a process, which allows them to exchange data and coordinate their activities through shared variables, objects, and specialized synchronization mechanisms provided by the threading module.

    To facilitate inter-thread communication, the threading module provides various synchronization primitives like, Locks, Events, Conditions, and Semaphores objects. In this tutorial you will learn how to use the Event and Condition object for providing the communication between threads in a multi-threaded program.

    The Event Object

    An Event object manages the state of an internal flag so that threads can wait or set. Event object provides methods to control the state of this flag, allowing threads to synchronize their activities based on shared conditions.

    The flag is initially false and becomes true with the set() method and reset to false with the clear() method. The wait() method blocks until the flag is true.

    Following are the key methods of the Event object −

    • is_set(): Return True if and only if the internal flag is true.
    • set(): Set the internal flag to true. All threads waiting for it to become true are awakened. Threads that call wait() once the flag is true will not block at all.
    • clear(): Reset the internal flag to false. Subsequently, threads calling wait() will block until set() is called to set the internal flag to true again.
    • wait(timeout=None): Block until the internal flag is true. If the internal flag is true on entry, return immediately. Otherwise, block until another thread calls set() to set the flag to true, or until the optional timeout occurs. When the timeout argument is present and not None, it should be a floating point number specifying a timeout for the operation in seconds.

    Example

    The following code attempts to simulate the traffic flow being controlled by the state of traffic signal either GREEN or RED.

    There are two threads in the program, targeting two different functions. The signal_state() function periodically sets and resets the event indicating change of signal from GREEN to RED.

    The traffic_flow() function waits for the event to be set, and runs a loop till it remains set.

    from threading import Event, Thread
    import time
    
    terminate =Falsedefsignal_state():global terminate
    
    whilenot terminate:
        time.sleep(0.5)print("Traffic Police Giving GREEN Signal")
        event.set()
        time.sleep(1)print("Traffic Police Giving RED Signal")
        event.clear()deftraffic_flow():global terminate
    num =0while num <10andnot terminate:print("Waiting for GREEN Signal")
        event.wait()print("GREEN Signal ... Traffic can move")while event.is_set()andnot terminate:
            num +=1print("Vehicle No:", num," Crossing the Signal")
            time.sleep(1)print("RED Signal ... Traffic has to wait")
    event = Event() t1 = Thread(target=signal_state) t2 = Thread(target=traffic_flow) t1.start() t2.start()# Terminate the threads after some time time.sleep(5) terminate =True# join all threads to complete t1.join() t2.join()print("Exiting Main Thread")

    Output

    On executing the above code you will get the following output −

    Waiting for GREEN Signal
    Traffic Police Giving GREEN Signal
    GREEN Signal ... Traffic can move
    Vehicle No: 1 Crossing the Signal
    Traffic Police Giving RED Signal
    RED Signal ... Traffic has to wait
    Waiting for GREEN Signal
    Traffic Police Giving GREEN Signal
    GREEN Signal ... Traffic can move
    Vehicle No: 2 Crossing the Signal
    Vehicle No: 3 Crossing the Signal
    Traffic Police Giving RED Signal
    Traffic Police Giving GREEN Signal
    Vehicle No: 4 Crossing the Signal
    Traffic Police Giving RED Signal
    RED Signal ... Traffic has to wait
    Traffic Police Giving GREEN Signal
    Traffic Police Giving RED Signal
    Exiting Main Thread
    The Condition Object

    The Condition object in Python's threading module provides a more advanced synchronization mechanism. It allows threads to wait for a notification from another thread before proceeding. The Condition object are always associated with a lock and provide mechanisms for signaling between threads.

    Following is the syntax of the threading.Condition() class −

    threading.Condition(lock=None)
    Below are the key methods of the Condition object −

    acquire(*args): Acquire the underlying lock. This method calls the corresponding method on the underlying lock; the return value is whatever that method returns.
    release(): Release the underlying lock. This method calls the corresponding method on the underlying lock; there is no return value.
    wait(timeout=None): This method releases the underlying lock, and then blocks until it is awakened by a notify() or notify_all() call for the same condition variable in another thread, or until the optional timeout occurs. Once awakened or timed out, it re-acquires the lock and returns.
    wait_for(predicate, timeout=None): This utility method may call wait() repeatedly until the predicate is satisfied, or until a timeout occurs. The return value is the last return value of the predicate and will evaluate to False if the method timed out.
    notify(n=1): This method wakes up at most n of the threads waiting for the condition variable; it is a no-op if no threads are waiting.
    notify_all(): Wake up all threads waiting on this condition. This method acts like notify(), but wakes up all waiting threads instead of one. If the calling thread has not acquired the lock when this method is called, a RuntimeError is raised.
    Example

    This example demonstrates a simple form of inter-thread communication using the Condition object of the Python's threading module. Here thread_a and thread_b are communicated using a Condition object, the thread_a waits until it receives a notification from thread_b. the thread_b sleeps for 2 seconds before notifying thread_a and then finishes.

    from threading import Condition, Thread
    import time

    c = Condition()

    def thread_a():
    print("Thread A started")
    with c:
    print("Thread A waiting for permission...")
    c.wait()
    print("Thread A got permission!")
    print("Thread A finished")

    def thread_b():
    print("Thread B started")
    with c:
    time.sleep(2)
    print("Notifying Thread A...")
    c.notify()
    print("Thread B finished")

    Thread(target=thread_a).start()
    Thread(target=thread_b).start()
    Output

    On executing the above code you will get the following output −

    Thread A started
    Thread A waiting for permission...
    Thread B started
    Notifying Thread A...
    Thread B finished
    Thread A got permission!
    Thread A finished
    Example

    Here is another code demonstrating how the Condition object is used for providing the communication between threads. In this, the thread t2 runs the taskB() function, and the thread t1 runs the taskA() function. The t1 thread acquires the condition and notifies it.

    By that time, the t2 thread is in a waiting state. After the condition is released, the waiting thread proceeds to consume the random number generated by the notifying function.

    from threading import Condition, Thread
    import time
    import random

    numbers = []

    def taskA(c):
    for _ in range(5):
    with c:
    num = random.randint(1, 10)
    print("Generated random number:", num)
    numbers.append(num)
    print("Notification issued")
    c.notify()
    time.sleep(0.3)

    def taskB(c):
    for i in range(5):
    with c:
    print("waiting for update")
    while not numbers:
    c.wait()
    print("Obtained random number", numbers.pop())
    time.sleep(0.3)

    c = Condition()
    t1 = Thread(target=taskB, args=(c,))
    t2 = Thread(target=taskA, args=(c,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print("Done")
    When you execute this code, it will produce the following output −

    waiting for update
    Generated random number: 2
    Notification issued
    Obtained random number 2
    Generated random number: 5
    Notification issued
    waiting for update
    Obtained random number 5
    Generated random number: 1
    Notification issued
    waiting for update
    Obtained random number 1
    Generated random number: 9
    Notification issued
    waiting for update
    Obtained random number 9
    Generated random number: 2
    Notification issued
    waiting for update
    Obtained random number 2
    Done
  •  Synchronizing Threads

    In Python, when multiple threads are working concurrently with shared resources, it’s important to synchronize their access to maintain data integrity and program correctness. Synchronizing threads in python can be achieved using various synchronization primitives provided by the threading module, such as locks, conditions, semaphores, and barriers to control access to shared resources and coordinate the execution of multiple threads.

    In this tutorial, we’ll learn about various synchronization primitives provided by Python’s threading module.

    Thread Synchronization using Locks

    The lock object in the Python’s threading module provide the simplest synchronization primitive. They allow threads to acquire and release locks around critical sections of code, ensuring that only one thread can execute the protected code at a time.

    A new lock is created by calling the Lock() method, which returns a lock object. The lock can be acquired using the acquire(blocking) method, which force the threads to run synchronously. The optional blocking parameter enables you to control whether the thread waits to acquire the lock and released using the release() method.

    Example

    The following example demonstrates how to use locks (the threading.Lock() method) to synchronize threads in Python, ensuring that multiple threads access shared resources safely and correctly.

    import threading
    
    counter =10defincrement(theLock, N):global counter
       for i inrange(N):
    
      theLock.acquire()
      counter +=1
      theLock.release()
    lock = threading.Lock() t1 = threading.Thread(target=increment, args=[lock,2]) t2 = threading.Thread(target=increment, args=[lock,10]) t3 = threading.Thread(target=increment, args=[lock,4]) t1.start() t2.start() t3.start()# Wait for all threads to completefor thread in(t1, t2, t3): thread.join()print("All threads have completed")print("The Final Counter Value:", counter)

    Output

    When the above code is executed, it produces the following output −

    All threads have completed
    The Final Counter Value: 26
    Condition Objects for Synchronizing Python Threads

    Condition variables enable threads to wait until notified by another thread. They are useful for providing communication between the threads. The wait() method is used to block a thread until it is notified by another thread through notify() or notify_all().

    Example

    This example demonstrates how Condition objects can synchronize threads using the notify() and wait() methods.

    import threading

    counter = 0

    # Consumer function
    def consumer(cv):
    global counter
    with cv:
    print("Consumer is waiting")
    cv.wait() # Wait until notified by increment
    print("Consumer has been notified. Current Counter value:", counter)

    # increment function
    def increment(cv, N):
    global counter
    with cv:
    print("increment is producing items")
    for i in range(1, N + 1):
    counter += i # Increment counter by i

    # Notify the consumer
    cv.notify()
    print("Increment has finished")

    # Create a Condition object
    cv = threading.Condition()

    # Create and start threads
    consumer_thread = threading.Thread(target=consumer, args=[cv])
    increment_thread = threading.Thread(target=increment, args=[cv, 5])

    consumer_thread.start()
    increment_thread.start()

    consumer_thread.join()
    increment_thread.join()

    print("The Final Counter Value:", counter)
    Output

    On executing the above program, it will produce the following output −

    Consumer is waiting
    increment is producing items
    Increment has finished
    Consumer has been notified. Current Counter value: 15
    The Final Counter Value: 15
    Synchronizing threads using the join() Method

    The join() method in Python's threading module is used to wait until all threads have completed their execution. This is a straightforward way to synchronize the main thread with the completion of other threads.

    Example

    This demonstrates synchronization of threads using the join() method to ensure that the main thread waits for all started threads to complete their work before proceeding.

    import threading
    import time

    class MyThread(threading.Thread):
    def __init__(self, threadID, name, counter):
    threading.Thread.__init__(self)
    self.threadID = threadID
    self.name = name
    self.counter = counter

    def run(self):
    print("Starting " + self.name)
    print_time(self.name, self.counter, 3)

    def print_time(threadName, delay, counter):
    while counter:
    time.sleep(delay)
    print("%s: %s" % (threadName, time.ctime(time.time())))
    counter -= 1

    threads = []

    # Create new threads
    thread1 = MyThread(1, "Thread-1", 1)
    thread2 = MyThread(2, "Thread-2", 2)

    # Start the new Threads
    thread1.start()
    thread2.start()

    # Join the threads
    thread1.join()
    thread2.join()

    print("Exiting Main Thread")
    Output

    On executing the above program, it will produce the following output −

    Starting Thread-1
    Starting Thread-2
    Thread-1: Mon Jul 1 16:05:14 2024
    Thread-2: Mon Jul 1 16:05:15 2024
    Thread-1: Mon Jul 1 16:05:15 2024
    Thread-1: Mon Jul 1 16:05:16 2024
    Thread-2: Mon Jul 1 16:05:17 2024
    Thread-2: Mon Jul 1 16:05:19 2024
    Exiting Main Thread
    Additional Synchronization Primitives

    In addition to the above synchronization primitives, Python's threading module offers: −

    RLocks (Reentrant Locks): A variant of locks that allow a thread to acquire the same lock multiple times before releasing it, useful in recursive functions or nested function calls.
    Semaphores:Similar to locks but with a counter. Threads can acquire the semaphore up to a certain limit defined during initialization. Semaphores are useful for limiting access to resources with a fixed capacity.
    Barriers: Allows a fixed number of threads to synchronize at a barrier point and continue executing only when all threads have reached that point. Barriers are useful for coordinating a group of threads that must all complete a certain phase of execution before any of them can proceed further.
  • Daemon Threads

    Daemon threads in Python are useful for running background tasks that are not critical to the program’s operation. They allow you to run tasks in the background without worrying about keeping track of them.

    Python provides two types of threads: non-daemon and daemon threads. By default, threads are non-daemon threads. This tutorial provides a detailed explanation with relevant examples about daemon threads in Python programming.

    Overview of Daemon Threads

    Sometimes, it is necessary to execute a task in the background. A special type of thread is used for background tasks, called a daemon thread. In other words, daemon threads execute tasks in the background. These threads handle non-critical tasks that may be useful to the application but do not hamper it if they fail or are canceled mid-operation.

    Also, a daemon thread will not have control over when it is terminated. The program will terminate once all non-daemon threads finish, even if there are daemon threads still running at that point of time.

    Difference Between Daemon & Non-Daemon Threads

    DaemonNon-daemon
    A process will exit if only daemon threads are running (or if no threads are running).A process will not exit if at least one non-daemon thread is running.
    Daemon threads are used for background tasks.Non-daemon threads are used for critical tasks.
    Daemon threads are terminated abruptly.Non-daemon threads run to completion.

    Daemon threads can perform tasks such as −

    • Create a file that stores Log information in the background.
    • Perform web scraping in the background.
    • Save the data automatically into a database in the background.

    Creating a Daemon Thread in Python

    To create a daemon thread, you need to set the daemon property to True of the Thread constructor.

    t1=threading.Thread(daemon=True)

    By default the daemon property is set to None, If you change it to not None, daemon explicitly sets whether the thread is daemonic.

    Example

    Take a look at the following example to create a daemon thread and check whether the thread daemonic or not using the daemon attribute.

    import threading 
    from time import sleep
    
    # function to be executed in a new threaddefrun():# get the current thread
       thread = threading.current_thread()# is it a daemon thread?print(f'Daemon thread: {thread.daemon}')# Create a new thread and set it as daemon
    thread = threading.Thread(target=run, daemon=True)# start the thread
    thread.start()print('Is Main Thread is Daemon thread:', threading.current_thread().daemon)# Block for a short time to allow the daemon thread to run
    sleep(0.5)

    It will produce the following output −

    Daemon thread: True
    Is Main Thread is Daemon thread: False
    

    If a thread object is created in the main thread without any parameters, then the created thread will be a non-daemon thread because the main thread is not a daemon thread. Therefore, all threads created in the main thread default to non-daemon. However, we can change the daemon property to True by using the Thread.daemon attribute before starting the thread, nothing but before calling the start() method.

    Example

    Here is an example −

    import threading 
    from time import sleep
    
    # function to be executed in a new threaddefrun():# get the current thread
       thread = threading.current_thread()# is it a daemon thread?print(f'Daemon thread: {thread.daemon}')# Create a new thread  
    thread = threading.Thread(target=run)# Using the daemon property set the thread as daemon before starting the thread
    thread.daemon =True# start the thread
    thread.start()print('Is Main Thread is Daemon thread:', threading.current_thread().daemon)# Block for a short time to allow the daemon thread to run
    sleep(0.5)

    On executing the above program, we will get the following output −

    Daemon thread: True
    Is Main Thread is Daemon thread: False
    

    Managing the Daemon Thread Attribute

    If you attempt to set the daemon status of a thread after starting it, then a RuntimeError will be raised.

    Example

    Here is another example that demonstrates the getting the RuntimeError when you try to set the daemon status of a thread after starting it.

    from time import sleep
    from threading import current_thread
    from threading import Thread
    
    # function to be executed in a new threaddefrun():# get the current thread
       thread = current_thread()# is it a daemon thread?print(f'Daemon thread: {thread.daemon}')
       thread.daemon =True# create a new thread
    thread = Thread(target=run)# start the new thread
    thread.start()# block for a 0.5 sec for daemon thread to run
    sleep(0.5)

    It will produce the following output −

    Daemon thread: False
    Exception in thread Thread-1 (run):
    Traceback (most recent call last):
       . . . .
       . . . .
    
    thread.daemon = True
    File "/usr/lib/python3.10/threading.py", line 1203, in daemon
    raise RuntimeError("cannot set daemon status of active thread")
    RuntimeError: cannot set daemon status of active thread
  • Thread Priority

    In Python, currently thread priority is not directly supported by the threadingmodule. unlike Java, Python does not support thread priorities, thread groups, or certain thread control mechanisms like destroying, stopping, suspending, resuming, or interrupting threads.

    Even thought Python threads are designed simple and is loosely based on Java’s threading model. This is because of Python’s Global Interpreter Lock (GIL), which manages Python threads.

    However, you can simulate priority-based behavior using techniques such as sleep durations, custom scheduling logic within threads or using the additional module which manages task priorities.

    Setting the Thread Priority Using Sleep()

    You can simulate thread priority by introducing delays or using other mechanisms to control the execution order of threads. One common approach to simulate thread priority is by adjusting the sleep duration of your threads.

    Threads with a lower priority sleep longer, and threads with a high priority sleep shorter.

    Example

    Here’s a simple example to demonstrate how to customize the thread priorities using the delays in Python threads. In this example, Thread-2 completes before Thread-1 because it has a lower priority value, resulting in a shorter sleep time.

    import threading
    import time
    
    classDummyThread(threading.Thread):def__init__(self, name, priority):
    
      threading.Thread.__init__(self)
      self.name = name
      self.priority = priority
    defrun(self):
      name = self.name
      time.sleep(1.0* self.priority)print(f"{name} thread with priority {self.priority} is running")# Creating threads with different priorities
    t1 = DummyThread(name='Thread-1', priority=4) t2 = DummyThread(name='Thread-2', priority=1)# Starting the threads t1.start() t2.start()# Waiting for both threads to complete t1.join() t2.join()print('All Threads are executed')

    Output

    On executing the above program, you will get the following results −

    Thread-2 thread with priority 1 is running
    Thread-1 thread with priority 4 is running
    All Threads are executed
    Adjusting Python Thread Priority on Windows

    On Windows Operating system you can manipulate the thread priority using the ctypes module, This is one of the Pythons standard module used for interacting with the Windows API.

    Example

    This example demonstrates how to manually set the priority of threads in Python on a Windows system using the ctypes module.

    import threading
    import ctypes
    import time

    # Constants for Windows API
    w32 = ctypes.windll.kernel32
    SET_THREAD = 0x20
    PRIORITIZE_THE_THREAD = 1

    class MyThread(threading.Thread):
    def __init__(self, start_event, name, iterations):
    super().__init__()
    self.start_event = start_event
    self.thread_id = None
    self.iterations = iterations
    self.name = name

    def set_priority(self, priority):
    if not self.is_alive():
    print('Cannot set priority for a non-active thread')
    return

    thread_handle = w32.OpenThread(SET_THREAD, False, self.thread_id)
    success = w32.SetThreadPriority(thread_handle, priority)
    w32.CloseHandle(thread_handle)
    if not success:
    print('Failed to set thread priority:', w32.GetLastError())

    def run(self):
    self.thread_id = w32.GetCurrentThreadId()
    self.start_event.wait()
    while self.iterations:
    print(f"{self.name} running")
    start_time = time.time()
    while time.time() - start_time < 1:
    pass
    self.iterations -= 1

    # Create an event to synchronize thread start
    start_event = threading.Event()

    # Create threads
    thread_normal = MyThread(start_event, name='normal', iterations=4)
    thread_high = MyThread(start_event, name='high', iterations=4)

    # Start the threads
    thread_normal.start()
    thread_high.start()

    # Adjusting priority of 'high' thread
    thread_high.set_priority(PRIORITIZE_THE_THREAD)

    # Trigger thread execution
    start_event.set()
    Output

    While executing this code in your Python interpreter, you will get the following results −

    high running
    normal running
    high running
    normal running
    high running
    normal running
    high running
    normal running
    Prioritizing Python Threads Using the Queue Module

    The queue module in Python's standard library is useful in threaded programming when information must be exchanged safely between multiple threads. The Priority Queue class in this module implements all the required locking semantics.

    With a priority queue, the entries are kept sorted (using the heapq module) and the lowest valued entry is retrieved first.

    The Queue objects have following methods to control the Queue −

    get() − The get() removes and returns an item from the queue.
    put() − The put adds item to a queue.
    qsize() − The qsize() returns the number of items that are currently in the queue.
    empty() − The empty( ) returns True if queue is empty; otherwise, False.
    full() − the full() returns True if queue is full; otherwise, False.
    queue.PriorityQueue(maxsize=0)
    This is the Constructor for a priority queue. maxsize is an integer that sets the upper limit on the number of items that can be placed in the queue. If maxsize is less than or equal to zero, the queue size is infinite.

    The lowest valued entries are retrieved first (the lowest valued entry is the one that would be returned by min(entries)). A typical pattern for entries is a tuple in the form −

    (priority_number, data)
    Example

    This example demonstrates the use of the PriorityQueue class in the queue module to manage task priorities between the two threads.

    Open Compiler
    from time import sleep
    from random import random, randint
    from threading import Thread
    from queue import PriorityQueue

    queue = PriorityQueue()

    def producer(queue):
    print('Producer: Running')
    for i in range(5):

    # create item with priority
    value = random()
    priority = randint(0, 5)
    item = (priority, value)
    queue.put(item)
    # wait for all items to be processed
    queue.join()

    queue.put(None)
    print('Producer: Done')

    def consumer(queue):
    print('Consumer: Running')

    while True:

    # get a unit of work
    item = queue.get()
    if item is None:
    break

    sleep(item[1])
    print(item)
    queue.task_done()
    print('Consumer: Done')

    producer = Thread(target=producer, args=(queue,))
    producer.start()

    consumer = Thread(target=consumer, args=(queue,))
    consumer.start()

    producer.join()
    consumer.join()
    Output

    On execution, It will produce the following output −

    Producer: Running
    Consumer: Running
    (0, 0.15332707626852804)
    (2, 0.4730737391435892)
    (2, 0.8679231358257962)
    (3, 0.051924220435665025)
    (4, 0.23945882716108446)
    Producer: Done
    Consumer: Done
  • Main Thread

    In Python, the main thread is the initial thread that starts when the Python interpreter is executed. It is the default thread within a Python process, responsible for managing the program and creating additional threads. Every Python program has at least one thread of execution called the main thread.

    The main thread by default is a non-daemon thread. In this tutorial you will see the detailed explanation with relevant examples about main thread in Python programming.

    Accessing the Main Thread

    The threading module in Python provides functions to access the threads. Here are the key functions −

    • threading.current_thread(): This function returns a threading.Thread instance representing the current thread.
    • threading.main_thread(): Returns a threading.Thread instance representing the main thread.

    Example

    The threading.current_thread() function returns a threading.Thread instance representing the current thread. Here is an example.

    import threading
    
    name ='Tutorialspoint'print('Output:', name)print(threading.current_thread())

    It will produce the following output −

    Output: Tutorialspoint
    <_MainThread(MainThread, started 140260292161536)>    
    

    Example

    This example demonstrates how to use the threading.main_thread() function to get a reference to the main thread. And it is also shows the difference between the main thread and other threads using threading.current_thread() function.

    import threading
    import time
    
    deffunc(x):
       time.sleep(x)ifnot threading.current_thread()is threading.main_thread():print('threading.current_thread() not threading.main_thread()')
    
    t = threading.Thread(target=func, args=(0.5,))
    t.start()print(threading.main_thread())print("Main thread finished")

    When the above code is executed, it produces the following result −

    <_MainThread(MainThread, started 140032182964224)>
    Main thread finished
    threading.current_thread() not threading.main_thread()
    Main Thread Behavior in Python

    The main thread will exit whenever it has finished executing all the code in your script that is not started in a separate thread. For instance, when you start a new thread using start() method, the main thread will continue to execute the remaining code in the script until it reaches the end and then exit.

    Since the other threads are started in a non-daemon mode by default, they will continue running until they are finished, even if the main thread has exited.

    Example

    The following example shows the main thread behavior in a python multithreaded program.

    import threading
    import time

    def func(x):
    print('Current Thread Details:',threading.current_thread())
    for n in range(x):
    print('Internal Thread Running', n)
    print('Internal Thread Finished...')

    t = threading.Thread(target=func, args=(6,))
    t.start()

    for i in range(3):
    print('Main Thread Running',i)
    print("Main Thread Finished...")
    It will produce the following output −

    Current Thread Details: Thread(Thread-1 (func), started 140562647860800)>
    Main Thread Running 0
    Internal Thread Running 0
    Main Thread Running 1
    Main Thread Running 2
    Internal Thread Running 1
    Main Thread Finished...
    Internal Thread Running 2
    Internal Thread Running 3
    Internal Thread Running 4
    Internal Thread Running 5
    Internal Thread Finished...
    The above code can produce different outputs for different runs and different compilers.
    Main Thread Waiting for Other Threads

    To ensure that the main thread waits for all other threads to finish, you can join the threads using the join() method. By using the join() method, you can control the execution flow and ensure that the main thread properly waits for all other threads to complete their tasks before exiting. This helps in managing the lifecycle of threads in a multi-threaded Python program effectively.

    Example

    This example demonstrates how to properly manage the main thread and ensure it does not exit before the worker threads have finished their tasks.

    from threading import Thread
    from time import sleep

    def my_function_1():
    print("Worker 1 started")
    sleep(1)
    print("Worker 1 done")

    def my_function_2(main_thread):
    print("Worker 2 waiting for Worker 1 to finish")
    main_thread.join()
    print("Worker 2 started")
    sleep(1)
    print("Worker 2 done")

    worker1 = Thread(target=my_function_1)
    worker2 = Thread(target=my_function_2, args=(worker1,))

    worker1.start()
    worker2.start()

    for num in range(6):
    print("Main thread is still working on task", num)
    sleep(0.60)

    worker1.join()
    print("Main thread Completed")
    When the above code is executed, it produces the following result −

    Worker 1 started
    Worker 2 waiting for Worker 1 to finish
    Main thread is still working on task 0
    Main thread is still working on task 1
    Worker 1 done
    Worker 2 started
    Main thread is still working on task 2
    Main thread is still working on task 3
    Worker 2 done
    Main thread is still working on task 4
    Main thread is still working on task 5
    Main thread Completed
  • Thread Pools

    A thread pool is a mechanism that automatically manages multiple threads efficiently, allowing tasks to be executed concurrently. Python does not provide thread pooling directly through the threading module.

    Instead, it offers thread-based pooling through the multiprocessing.dummymodule and the concurrent.futures module. These modules provide convenient interfaces for creating and managing thread pools, making it easier to perform concurrent task execution.

    What is a Thread Pool?

    A thread pool is a collection of threads that are managed by a pool. Each thread in the pool is called a worker or a worker thread. These threads can be reused to perform multiple tasks, which reduces the burden of creating and destroying threads repeatedly.

    Thread pools control the creation of threads and their life cycle, making them more efficient for handling large numbers of tasks.

    We can implement thread-pools in Python using the following classes −

    • Python ThreadPool Class
    • Python ThreadPoolExecutor Class

    Using Python ThreadPool Class

    The multiprocessing.pool.ThreadPool class provides a thread pool interface within the multiprocessing module. It manages a pool of worker threads to which jobs can be submitted for concurrent execution.

    A ThreadPool object simplifies the management of multiple threads by handling the creation and distribution of tasks among the worker threads. It shares an interface with the Pool class, originally designed for processes, but has been adjusted to work with threads too.

    ThreadPool instances are fully interface-compatible with Pool instances and should be managed either as a context manager or by calling close() and terminate() manually.

    Example

    This example demonstrates the parallel execution of the square and cube functions on the list of numbers using the Python thread pool, where each function is applied to the numbers concurrently with up to 3 threads, each with a delay of 1 second between executions.

    from multiprocessing.dummy import Pool as ThreadPool
    import time
    
    defsquare(number):
       sqr = number * number
       time.sleep(1)print("Number:{} Square:{}".format(number, sqr))defcube(number):
       cub = number*number*number
       time.sleep(1)print("Number:{} Cube:{}".format(number, cub))
    
    numbers =[1,2,3,4,5]
    pool = ThreadPool(3)
    pool.map(square, numbers)
    pool.map(cube, numbers)
    
    pool.close()

    Output

    On executing the above code you will get the following output −

    Number:2 Square:4
    Number:1 Square:1
    Number:3 Square:9
    Number:4 Square:16
    Number:5 Square:25
    Number:1 Cube:1
    Number:2 Cube:8
    Number:3 Cube:27
    Number:4 Cube:64
    Number:5 Cube:125
    Using Python ThreadPoolExecutor Class

    The ThreadPoolExecutor class of the Python the concurrent.futures module provides a high-level interface for asynchronously executing functions using threads. The concurrent.futures module includes Future class and two Executor classes − ThreadPoolExecutor and ProcessPoolExecutor.

    The Future Class

    The concurrent.futures.Future class is responsible for handling asynchronous execution of any callable such as a function. To obtain a Future object, you should call the submit() method on any Executor object. It should not be created directly by its constructor.

    Important methods in the Future class are −

    result(timeout=None): This method returns the value returned by the call. If the call hasn't yet completed, then this method will wait up to timeout seconds. If the call hasn't completed in timeout seconds, then a TimeoutError will be raised. If timeout is not specified, there is no limit to the wait time.
    cancel(): This method, attempt to cancel the call. If the call is currently being executed or finished running and cannot be cancelled then the method will return a boolean value False. Otherwise the call will be cancelled and the method returns True.
    cancelled(): Returns True if the call was successfully cancelled.
    running(): Returns True if the call is currently being executed and cannot be cancelled.
    done(): Returns True if the call was successfully cancelled or finished running.
    The ThreadPoolExecutor Class

    This class represents a pool of specified number maximum worker threads to execute calls asynchronously.

    concurrent.futures.ThreadPoolExecutor(max_threads)
    Example

    Here is an example that uses the concurrent.futures.ThreadPoolExecutor class to manage and execute tasks asynchronously in Python. Specifically, it shows how to submit multiple tasks to a thread pool and how to check their execution status.

    from concurrent.futures import ThreadPoolExecutor
    from time import sleep
    def square(numbers):
    for val in numbers:
    ret = val*val
    sleep(1)
    print("Number:{} Square:{}".format(val, ret))
    def cube(numbers):
    for val in numbers:
    ret = val*val*val
    sleep(1)
    print("Number:{} Cube:{}".format(val, ret))
    if __name__ == '__main__':
    numbers = [1,2,3,4,5]
    executor = ThreadPoolExecutor(4)
    thread1 = executor.submit(square, (numbers))
    thread2 = executor.submit(cube, (numbers))
    print("Thread 1 executed ? :",thread1.done())
    print("Thread 2 executed ? :",thread2.done())
    sleep(2)
    print("Thread 1 executed ? :",thread1.done())
    print("Thread 2 executed ? :",thread2.done())
    It will produce the following output −

    Thread 1 executed ? : False
    Thread 2 executed ? : False
    Number:1 Square:1
    Number:1 Cube:1
    Thread 1 executed ? : False
    Thread 2 executed ? : False
    Number:2 Square:4
    Number:2 Cube:8
    Number:3 Square:9
    Number:3 Cube:27
    Number:4 Square:16
    Number:4 Cube:64
    Number:5 Square:25
    Number:5 Cube:125
  • Thread Schedulin

    Thread scheduling in Python is a process of deciding which thread runs at any given time. In a multi-threaded program, multiple threads are executed independently, allowing for parallel execution of tasks. However, Python does not have built-in support for controlling thread priorities or scheduling policies directly. Instead, it relies on the operating system’s thread scheduler.

    Python threads are mapped to native threads of the host operating system, such as POSIX threads (pthreads) on Unix-like systems or Windows threads. The operating system’s scheduler manages the execution of these threads, including context switching, thread priorities, and scheduling policies. Python provides basic thread scheduling capabilities through the threading.Timer class and the schedmodule.

    In this tutorial will learn the basics of thread scheduling in Python, including how to use the sched module for scheduling tasks and the threading.Timer class for delayed execution of functions.

    Scheduling Threads using the Timer Class

    The Timer class of the Python threading module allows you to schedule a function to be called after a certain amount of time. This class is a subclass of Thread and serves as an example of creating custom threads.

    You start a timer by calling its start() method, similar to threads. If needed, you can stop the timer before it begins by using the cancel() method. Note that the actual delay before the action is executed might not match the exact interval specified.

    Example

    This example demonstrates how to use the threading.Timer() class to schedule and manage the execution of tasks (custom threads) in Python.

    import threading
    import time
    
    # Define the event functiondefschedule_event(name, start):
       now = time.time()
       elapsed =int(now - start)print('Elapsed:', elapsed,'Name:', name)# Start time
    start = time.time()print('START:', time.ctime(start))# Schedule events using Timer
    t1 = threading.Timer(3, schedule_event, args=('EVENT_1', start))
    t2 = threading.Timer(2, schedule_event, args=('EVENT_2', start))# Start the timers
    t1.start()
    t2.start()
    
    t1.join()
    t2.join()# End time
    end = time.time()print('End:', time.ctime(end))

    On executing the above program, it will produce the following output −

    START: Tue Jul  2 14:46:33 2024
    Elapsed: 2 Name: EVENT_2
    Elapsed: 3 Name: EVENT_1
    End: Tue Jul 2 14:46:36 2024
    Scheduling Threads using the sched Module

    The sched module in Python's standard library provides a way to schedule tasks. It implements a generic event scheduler for running tasks at specific times. It provides similar tools like task scheduler in windows or Linux.

    Key Classes and Methods of the sched Module

    The scheduler() class is defined in the sched module is used to create a scheduler object. Here is the syntax of the class −

    scheduler(timefunc=time.monotonic, delayfunc=time.sleep)
    The methods defined in scheduler class include −

    scheduler.enter(delay, priority, action, argument=(), kwargs={}) − Events can be scheduled to run after a delay, or at a specific time. To schedule them with a delay, enter() method is used.
    scheduler.cancel(event) − Remove the event from the queue. If the event is not an event currently in the queue, this method will raise a ValueError.
    scheduler.run(blocking=True) − Run all scheduled events.
    Events can be scheduled to run after a delay, or at a specific time. To schedule them with a delay, use the enter() method, which takes four arguments.

    A number representing the delay
    A priority value
    The function to call
    A tuple of arguments for the function
    Example

    This example demonstrates how to schedule events to run after a delay using the sched module. It schedules two different events −

    import sched
    import time

    scheduler = sched.scheduler(time.time, time.sleep)

    def schedule_event(name, start):
    now = time.time()
    elapsed = int(now - start)
    print('elapsed=',elapsed, 'name=', name)

    start = time.time()
    print('START:', time.ctime(start))
    scheduler.enter(2, 1, schedule_event, ('EVENT_1', start))
    scheduler.enter(5, 1, schedule_event, ('EVENT_2', start))

    scheduler.run()

    # End time
    end = time.time()
    print('End:', time.ctime(end))
    It will produce the following output −

    START: Tue Jul 2 15:11:48 2024
    elapsed= 2 name= EVENT_1
    elapsed= 5 name= EVENT_2
    End: Tue Jul 2 15:11:53 2024
    Example

    Let's take another example to understand the concept better. This example schedules a function to perform an addition after a 4-second delay using the sched module in Python.

    import sched
    from datetime import datetime
    import time

    def addition(a,b):
    print("Performing Addition : ", datetime.now())
    print("Time : ", time.monotonic())
    print("Result {}+{} =".format(a, b), a+b)

    s = sched.scheduler()

    print("Start Time : ", datetime.now())

    event1 = s.enter(4, 1, addition, argument = (5,6))
    print("Event Created : ", event1)
    s.run()
    print("End Time : ", datetime.now())
    It will produce the following output −

    Start Time : 2024-07-02 15:18:27.862524
    Event Created : Event(time=2927111.05638099, priority=1, sequence=0, action=<function addition at 0x7f31f902bd90>, argument=(5, 6), kwargs={})
    Performing Addition : 2024-07-02 15:18:31.866381
    Time : 2927111.060294749
    Result 5+6 = 11
    End Time : 2024-07-02 15:18:31.866545
  • Naming the Threads

    In Python, naming a thread involves assigning a string as an identifier to the thread object. Thread names in Python are primarily used for identification purposes only and do not affect the thread’s behavior or semantics. Multiple threads can share the same name, and names can be specified during the thread’s initialization or changed dynamically.

    Thread naming in Python provides a straightforward way to identify and manage threads within a concurrent program. By assigning meaningful names, users can enhance code clarity and easily debug the complex multi-threaded applications.

    Naming the Threads in Python

    When you create a thread using threading.Thread() class, you can specify its name using the name parameter. If not provided, Python assigns a default name like the following pattern “Thread-N”, where N is a small decimal number. Alternatively, if you specify a target function, the default name format becomes “Thread-N (target_function_name)”.

    Example

    Here is an example demonstrates assigning custom and default names to threads created using threading.Thread() class, and displays how names can reflect target functions.

    from threading import Thread
    import threading
    from time import sleep
    
    defmy_function_1(arg):print("This tread name is", threading.current_thread().name)# Create thread objects
    thread1 = Thread(target=my_function_1, name='My_thread', args=(2,))
    thread2 = Thread(target=my_function_1, args=(3,))print("This tread name is", threading.current_thread().name)# Start the first thread and wait for 0.2 seconds
    thread1.start()
    thread1.join()# Start the second thread and wait for it to complete
    thread2.start()
    thread2.join()

    On executing the above, it will produce the following results −

    This tread name is MainThread
    This tread name is My_thread
    This tread name is Thread-1 (my_function_1)

    Dynamically Assigning Names to the Python Threads

    You can assign or change a thread’s name dynamically by directly modifying the name attribute of the thread object.

    Example

    This example shows how to dynamically change thread names by modifying the name attribute of the thread object.

    from threading import Thread
    import threading
    from time import sleep

    def my_function_1(arg):
    threading.current_thread().name = "custom_name"
    print("This tread name is", threading.current_thread().name)

    # Create thread objects
    thread1 = Thread(target=my_function_1, name='My_thread', args=(2,))
    thread2 = Thread(target=my_function_1, args=(3,))

    print("This tread name is", threading.current_thread().name)

    # Start the first thread and wait for 0.2 seconds
    thread1.start()
    thread1.join()

    # Start the second thread and wait for it to complete
    thread2.start()
    thread2.join()

    When you execute the above code, it will produce the following results −

    This tread name is MainThread
    This tread name is custom_name
    This tread name is custom_name

    Example

    Threads can be initialized with custom names and even renamed after creation. This example demonstrates creating threads with custom names and modifying a thread’s name after creation.

    import threading

    def addition_of_numbers(x, y):
    print("This Thread name is :", threading.current_thread().name)
    result = x + y

    def cube_number(i):
    result = i ** 3
    print("This Thread name is :", threading.current_thread().name)

    def basic_function():
    print("This Thread name is :", threading.current_thread().name)

    # Create threads with custom names
    t1 = threading.Thread(target=addition_of_numbers, name='My_thread', args=(2, 4))
    t2 = threading.Thread(target=cube_number, args=(4,))
    t3 = threading.Thread(target=basic_function)

    # Start and join threads
    t1.start()
    t1.join()

    t2.start()
    t2.join()

    t3.name = 'custom_name' # Assigning name after thread creation
    t3.start()
    t3.join()

    print(threading.current_thread().name) # Print main thread's name

    Upon execution, the above code will produce the following results −

    This Thread name is : My_thread
    This Thread name is : Thread-1 (cube_number)
    This Thread name is : custom_name
    MainThread