Author: Saim Khalid

  • 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

  • Joining the Threads

    In Python, joining the threads means using the join() method to wait for one thread to finish before moving on to others. This is useful in multithreaded programming to make sure some threads are completed before starting or continuing with other threads. By using the join() method, you can make sure that one thread has finished running before another thread or the main program continues. In this tutorial you will get the detailed explain of the join() method with suitable examples.

    Joining the Threads in Python

    To join the threads in Python, you can use the Thread.join() method from the threading module. Which generally is used to block the calling thread until the thread on which join() was called terminates. The termination may be either normal, because of an unhandled exception or until the optional timeout occurs. You can call join() multiple times. However, if you try to join the current thread or attempts to join a thread before starting it with the start() method, will raise the RuntimeError exception.

    Following is the syntax of the Thread.join() method −

    thread.join(timeout)

    Where, the timeout is an optional parameter that takes a floating-point number specifying the maximum wait time in seconds (or fractions thereof). If it is not provided or None, the method will block until the thread terminates.

    This method always returns None. After calling join(), you can use is_alive() to check if the thread is still running. This is useful to determine if the join() call timed out.

    Example

    The following example demonstrates the use of join() in a multithreaded program. It starts two threads (thread1 and thread2). Initially, it blocks the main thread until thread1 finishes executing the my_function_1. After thread1 completes, thread2.start() is called, followed by thread2.join() to ensure that the main thread waits until thread2 finishes executing my_function_2().

    from threading import Thread
    from time import sleep
    
    defmy_function_1(arg):for i inrange(arg):print("Child Thread 1 running", i)
    
      sleep(0.5)defmy_function_2(arg):for i inrange(arg):print("Child Thread 2 running", i)
      sleep(0.1)# Create thread objects
    thread1 = Thread(target=my_function_1, args=(5,)) thread2 = Thread(target=my_function_2, args=(3,))# Start the first thread and wait for it to complete thread1.start() thread1.join()# Start the second thread and wait for it to complete thread2.start() thread2.join()print("Main thread finished...exiting")

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

    Child Thread 1 running 0
    Child Thread 1 running 1
    Child Thread 1 running 2
    Child Thread 1 running 3
    Child Thread 1 running 4
    Child Thread 2 running 0
    Child Thread 2 running 1
    Child Thread 2 running 2
    Main thread finished...exiting
    

    Example

    Here is another example that demonstrates how the join() method with a timeout allows waiting for a thread to complete for a specified period, then proceeding even if the thread hasn’t finished.

    from threading import Thread
    from time import sleep
    
    defmy_function_1(arg):for i inrange(arg):print("Child Thread 1 running", i)
    
      sleep(0.5)defmy_function_2(arg):for i inrange(arg):print("Child Thread 2 running", i)
      sleep(0.1)# Create thread objects
    thread1 = Thread(target=my_function_1, args=(5,)) thread2 = Thread(target=my_function_2, args=(3,))# Start the first thread and wait for 0.2 seconds thread1.start() thread1.join(timeout=0.2)# Start the second thread and wait for it to complete thread2.start() thread2.join()print("Main thread finished...exiting")

    When you run the above code, you can see the following output −

    Child Thread 1 running 0
    Child Thread 2 running 0
    Child Thread 2 running 1
    Child Thread 2 running 2
    Child Thread 1 running 1
    Main thread finished...exiting
    Child Thread 1 running 2
    Child Thread 1 running 3
    Child Thread 1 running 4
  • Starting a Thread

    In Python, starting a thread involves using the start() method provided by the Thread class in the threading module. This method initiates the thread’s activity and automatically calls its run() method in a separate thread of execution. Meaning that, when you call start() on each thread object (for example., thread1, thread2, thread3) to initiate their execution.

    Python to launch separate threads that concurrently execute the run() method defined in each Thread instance. And the main thread continues its execution after starting the child threads. 

    In this tutorial, you will see a detailed explanation and example of how to use the start() method effectively in multi-threaded programming to understand its behavior in multi-thread applications.

    Starting a Thread in Python

    The start() method is fundamental for beginning the execution of a thread. It sets up the thread’s environment and schedules it to run. Importantly, it should only be called once per Thread object. If this method is called more than once on the same Thread object, it will raise a RuntimeError.

    Here is the syntax for using the start() method on a Thread object −

    threading.thread.start()

    Example

    let’s see the below example, that demonstrates how to start a new thread in Python using the start() method.

    from threading import Thread
    from time import sleep
    
    defmy_function(arg):for i inrange(arg):print("child Thread running", i)
    
      sleep(0.5)
    thread = Thread(target = my_function, args =(10,)) thread.start()print("thread finished...exiting")

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

    child Thread running 0
    thread finished...exiting
    child Thread running 1
    child Thread running 2
    child Thread running 3
    child Thread running 4
    child Thread running 5
    child Thread running 6
    child Thread running 7
    child Thread running 8
    child Thread running 9
    

    Example

    Here is another example demonstrating the working of the start() method. You can observe that, by not calling the start() method on thread2, it remains inactive and does not begin execution.

    import threading
    import time
    
    classMyThread(threading.Thread):def__init__(self, threadID, name, counter):
    
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.counter = counter
    defrun(self):print("Starting "+ self.name)
      print_time(self.name, self.counter)print("Exiting "+ self.name)defprint_time(threadName, counter):while counter:
      time.sleep(1)print("%s: %s"%(threadName, time.ctime(time.time())))
      counter -=1# Create new threads
    thread1 = MyThread(1,"Thread-1",1) thread2 = MyThread(2,"Thread-2",2) thread3 = MyThread(3,"Thread-3",3)# Start new Threads thread1.start() thread3.start()print("Exiting Main Thread")

    The above code will produce the following output −

    Starting Thread-1
    Starting Thread-3
    Exiting Main Thread
    Thread-1: Mon Jun 24 18:24:59 2024
    Exiting Thread-1
    Thread-3: Mon Jun 24 18:24:59 2024
    Thread-3: Mon Jun 24 18:25:00 2024
    Thread-3: Mon Jun 24 18:25:01 2024
    Exiting Thread-3
    
  • Creating a Thread

    Creating a thread in Python involves initiating a separate flow of execution within a program, allowing multiple operations to run concurrently. This is particularly useful for performing tasks simultaneously, such as handling various I/O operations in parallel.

    Python provides multiple ways to create and manage threads. 

    • Creating a thread using the threading module is generally recommended due to its higher-level interface and additional functionalities.
    • On the other hand, the _thread module offers a simpler, lower-level approach to create and manage threads, which can be useful for straightforward, low-overhead threading tasks.

    In this tutorial, you will learn the basics of creating threads in Python using different approaches. We will cover creating threads using functions, extending the Thread class from the threading module, and utilizing the _thread module.

    Creating Threads with Functions

    You can create threads by using the Thread class from the threading module. In this approach, you can create a thread by simply passing a function to the Thread object. Here are the steps to start a new thread −

    • Define a function that you want the thread to execute.
    • Create a Thread object using the Thread class, passing the target function and its arguments.
    • Call the start method on the Thread object to begin execution.
    • Optionally, call the join method to wait for the thread to complete before proceeding.

    Example

    The following example demonstrates concurrent execution using threads in Python. It creates and starts multiple threads that execute different tasks concurrently by specifying user-defined functions as targets within the Threadclass.

    from threading import Thread
    
    defaddition_of_numbers(x, y):
       result = x + y
       print('Addition of {} + {} = {}'.format(x, y, result))defcube_number(i):
       result = i **3print('Cube of {} = {}'.format(i, result))defbasic_function():print("Basic function is running concurrently...")
    
    Thread(target=addition_of_numbers, args=(2,4)).start()  
    Thread(target=cube_number, args=(4,)).start() 
    Thread(target=basic_function).start()

    On executing the above program, it will produces the following result −

    Addition of 2 + 4 = 6
    Cube of 4 = 64
    Basic function is running concurrently...
    Creating Threads by Extending the Thread Class

    Another approach to creating a thread is by extending the Thread class. This approach involves defining a new class that inherits from Thread and overriding its __init__ and run methods. Here are the steps to start a new thread −

    Define a new subclass of the Thread class.
    Override the __init__ method to add additional arguments.
    Override the run method to implement the thread's behavior.
    Example

    This example demonstrates how to create and manage multiple threads using a custom MyThread class that extends the threading.Thread class in Python.

    import threading
    import time

    exitFlag = 0

    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, 5, self.counter)
    print ("Exiting " + self.name)

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

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

    # Start new Threads
    thread1.start()
    thread2.start()
    print ("Exiting Main Thread")
    When the above code is executed, it produces the following result −

    Starting Thread-1
    Starting Thread-2
    Exiting Main Thread
    Thread-1: Mon Jun 24 16:38:10 2024
    Thread-2: Mon Jun 24 16:38:11 2024
    Thread-1: Mon Jun 24 16:38:11 2024
    Thread-1: Mon Jun 24 16:38:12 2024
    Thread-2: Mon Jun 24 16:38:13 2024
    Thread-1: Mon Jun 24 16:38:13 2024
    Thread-1: Mon Jun 24 16:38:14 2024
    Exiting Thread-1
    Thread-2: Mon Jun 24 16:38:15 2024
    Thread-2: Mon Jun 24 16:38:17 2024
    Thread-2: Mon Jun 24 16:38:19 2024
    Exiting Thread-2
    Creating Threads using start_new_thread() Function

    The start_new_thread() function included in the _thread module is used to create a new thread in the running program. This module offers a low-level approach to threading. It is simpler but does not have some of the advanced features provided by the threading module.

    Here is the syntax of the _thread.start_new_thread() Function

    _thread.start_new_thread ( function, args[, kwargs] )
    This function starts a new thread and returns its identifier. The function parameter specifies the function that the new thread will execute. Any arguments required by this function can be passed using args and kwargs.

    Example

    import _thread
    import time
    # Define a function for the thread
    def thread_task( threadName, delay):
    for count in range(1, 6):
    time.sleep(delay)
    print ("Thread name: {} Count: {}".format ( threadName, count ))

    # Create two threads as follows
    try:
    _thread.start_new_thread( thread_task, ("Thread-1", 2, ) )
    _thread.start_new_thread( thread_task, ("Thread-2", 4, ) )
    except:
    print ("Error: unable to start thread")

    while True:
    pass

    thread_task("test", 0.3)
    It will produce the following output −

    Thread name: Thread-1 Count: 1
    Thread name: Thread-2 Count: 1
    Thread name: Thread-1 Count: 2
    Thread name: Thread-1 Count: 3
    Thread name: Thread-2 Count: 2
    Thread name: Thread-1 Count: 4
    Thread name: Thread-1 Count: 5
    Thread name: Thread-2 Count: 3
    Thread name: Thread-2 Count: 4
    Thread name: Thread-2 Count: 5
    Traceback (most recent call last):
    File "C:\Users\user\example.py", line 17, in <module>
    while True:
    KeyboardInterrupt
    The program goes in an infinite loop. You will have to press ctrl-c to stop.

  • Thread Life cycle

    A thread object goes through different stages during its life cycle. When a new thread object is created, it must be started, which calls the run() method of thread class. This method contains the logic of the process to be performed by the new thread. The thread completes its task as the run() method is over, and the newly created thread merges with the main thread.

    While a thread is running, it may be paused either for a predefined duration or it may be asked to pause till a certain event occurs. The thread resumes after the specified interval or the process is over.

    thread_life_cycle

    States of a Thread Life Cycle in Python

    Following are the stages of the Python Thread life cycle −

    • Creating a Thread − To create a new thread in Python, you typically use the Thread class from the threading module.
    • Starting a Thread − Once a thread object is created, it must be started by calling its start() method. This initiates the thread’s activity and invokes its run() method in a separate thread.
    • Paused/Blocked State − Threads can be paused or blocked for various reasons, such as waiting for I/O operations to complete or another thread to perform a task. This is typically managed by calling its join() method. This blocks the calling thread until the thread being joined terminates.
    • Synchronizing Threads − Synchronization ensures orderly execution and shared resource management among threads. This can be done by using synchronization primitives like locks, semaphores, or condition variables.
    • Termination − A thread terminates when its run() method completes execution, either by finishing its task or encountering an exception.

    Example: Python Thread Life Cycle Demonstration

    This example demonstrates the thread life cycle in Python by showing thread creation, starting, execution, and synchronization with the main thread.

    import threading
    
    deffunc(x):print('Current Thread Details:', threading.current_thread())for n inrange(x):print('{} Running'.format(threading.current_thread().name), n)print('Internal Thread Finished...')# Create thread objects
    t1 = threading.Thread(target=func, args=(2,))
    t2 = threading.Thread(target=func, args=(3,))# Start the threadsprint('Thread State: CREATED')
    t1.start()
    t2.start()# Wait for threads to complete
    t1.join()
    t2.join()print('Threads State: FINISHED')# Simulate main thread workfor i inrange(3):print('Main Thread Running', i)print("Main Thread Finished...")

    Output

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

    Thread State: CREATED
    Current Thread Details: <Thread(Thread-1 (func), started 140051032258112)>
    Thread-1 (func) Running 0
    Thread-1 (func) Running 1
    Internal Thread Finished...
    Current Thread Details: <Thread(Thread-2 (func), started 140051023865408)>
    Thread-2 (func) Running 0
    Thread-2 (func) Running 1
    Thread-2 (func) Running 2
    Internal Thread Finished...
    Threads State: FINISHED
    Main Thread Running 0
    Main Thread Running 1
    Main Thread Running 2
    Main Thread Finished...
    

    Example: Using a Synchronization Primitive

    Here is another example demonstrates the thread life cycle in Python, including creation, starting, running, and termination states, along with synchronization using a semaphore.

    import threading
    import time
    
    # Create a semaphore 
    semaphore = threading.Semaphore(2)defworker():with semaphore:print('{} has started working'.format(threading.current_thread().name))
    
      time.sleep(2)print('{} has finished working'.format(threading.current_thread().name))# Create a list to keep track of thread objects
    threads =[]# Create and start 5 threadsfor i inrange(5): t = threading.Thread(target=worker, name='Thread-{}'.format(i+1)) threads.append(t)print('{} has been created'.format(t.name)) t.start()# Wait for all threads to completefor t in threads: t.join()print('{} has terminated'.format(t.name))print('Threads State: All are FINISHED')print("Main Thread Finished...")

    Output

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

    Thread-1 has been created
    Thread-1 has started working
    Thread-2 has been created
    Thread-2 has started working
    Thread-3 has been created
    Thread-4 has been created
    Thread-5 has been created
    Thread-1 has finished working
    Thread-2 has finished working
    Thread-3 has started working
    Thread-1 has terminated
    Thread-2 has terminated
    Thread-4 has started working
    Thread-3 has finished working
    Thread-5 has started working
    Thread-3 has terminated
    Thread-4 has finished working
    Thread-4 has terminated
    Thread-5 has finished working
    Thread-5 has terminated
    Threads State: All are FINISHED
    Main Thread Finished...