Built with Tutify.io

    AIStudyLabs

    Python Threading Explained With Examples

    Python Threading Explained With Examples

    Python threading is a powerful feature that allows you to perform multiple tasks simultaneously. Threads are lightweight, independent units of execution that run concurrently within a process. In this article, we will explore how to create, manage, and work with threads in Python.

    <a id="h-1-what-is-threading-in-python"></a>

    1. What is Threading in Python?

    Threading is a way of achieving multitasking in Python. It allows a program to have multiple threads of execution simultaneously. Each thread runs independently and can perform different tasks concurrently. This means that if one thread is blocked or waiting for input/output, other threads can continue to run and keep the program responsive.

    Python provides a threading module that makes it easy to create and manage threads in a program. With this module, you can create multiple threads, start them, and synchronize their execution.

    Example of a thread in Python:

    
    # Import threading module
    import threading
    
    # Create function
    def my_function():
        print("I am text from MyFunc")
    
    # Create a new thread
    my_thread = threading.Thread(target=my_function)
    
    # Start the thread
    my_thread.start()
    

    When you run this code, you should see the message "I am text from MyFunc" printed to the console.

    <a id="h-2-thread-vs-process"></a>

    2. Thread vs Process

    In Python, both threads and processes are used for concurrent programming, but they differ in various ways. A process can be thought of as an instance of a program in execution, while a thread is a separate flow of execution within a process.

    See the Key differences between thread and process in Python from the following table:

    | Process | Thread | | --- | --- | | A process is a program in execution. | A thread is a lightweight process. | | Processes run independently of each other. | Threads share the same memory and can access the same variables and data structures. | | Each process has its own memory space. | Threads run within the memory space of the process. | | Processes are heavyweight and take more resources. | Threads are lightweight and require fewer resources. | | Processes communicate with each other through interprocess communication (IPC). | Threads communicate with each other through shared memory or message passing. | | Processes can run on different processors or cores. | Threads are limited to a single processor or core. |

    See the below example where we create one process and one thread:

    
    # Imports
    import multiprocessing
    import threading
    
    # Define a function to run as a process
    def process_function():
        print("This is a process")
    
    # Define a function to run as a thread
    def thread_function():
        print("This is a thread")
    
    if __name__ == "__main__":
        # Create a process
        process = multiprocessing.Process(target=process_function)
        process.start()
    
        # Create a thread
        thread = threading.Thread(target=thread_function)
        thread.start()
    

    <a id="h-3-why-use-python-threading"></a>

    3. Why use Python Threading?

    In traditional programming, a program runs sequentially from start to finish. Each line of code is executed one after another, and if a particular line of code takes a long time to execute, the program will become unresponsive.

    Python threading provides a solution to this problem by allowing multiple threads of execution to run concurrently within a single program. Each thread runs independently of the others so that if one thread becomes blocked waiting for an input/output operation to complete, the other threads can continue executing.

    This allows the program to remain responsive even when some parts of it are waiting for I/O operations to complete. However, working with thread is quite painful if you don't know how threads works in Python.

    <a id="h-4-creating-and-starting-threads-in-python"></a>

    4. Creating and Starting Threads in Python

    The process of creating and starting threads involves creating a thread object, specifying the target function, and then starting the thread using the start() method.

    <a id="h-4-1-creating-threads-with-threading-module"></a>

    4.1 Creating Threads with threading Module

    The threading module in Python provides a simple way to create threads. You can create a new thread by defining a function and then creating a Thread object from that function.

    
    # Import threading
    import threading
    
    def print_numbers():
        for i in range(1, 6):
            print(f"Thread 1: {i}")
    
    def print_letters():
        for letter in ["a", "b", "c"]:
            print(f"Thread 2: {letter}")
    
    thread1 = threading.Thread(target=print_numbers)
    thread2 = threading.Thread(target=print_letters)
    
    thread1.start()
    thread2.start()
    
    thread1.join()
    thread2.join()
    

    <a id="h-4-2-creating-threads-with-the-thread-decorator"></a>

    4.2 Creating Threads with the @thread Decorator

    Python provides a @thread decorator that allows you to create a thread from a function directly.

    
    # Import 
    import threading
    
    def thread(fn):
        def wrapper(*args, **kwargs):
            t = threading.Thread(target=fn, args=args, kwargs=kwargs)
            t.start()
            return t
        return wrapper
    
    @thread
    def my_function():
        print("Hello from a thread")
    
    my_function()
    

    <a id="h-4-3-joining-threads-to-wait-for-completion"></a>

    4.3 Joining Threads to Wait for Completion

    You can use the join() method to wait for a thread to complete its execution before moving on to the next task. The join() method blocks the calling thread and waits for the specified thread to terminate.

    
    import threading
    
    def worker():
        print("Worker thread executing")
    
    # Create a new thread
    t = threading.Thread(target=worker)
    
    # Start the thread
    t.start()
    
    # Wait for the thread to finish
    t.join()
    
    print("Thread Finished")
    

    <a id="h-5-thread-instance-attributes"></a>

    5. Thread Instance Attributes

    In Python threading, threads are instances of the Thread class. This class has several attributes that allow you to query or modify various aspects of the thread instance.

    <a id="h-5-1-query-thread-name"></a>

    5.1 Query Thread Name

    Thread Name is a readable identifier for the thread. By default, Python assigns a name to each thread created, such as "Thread-1", "Thread-2", and so on. However, you can also assign a custom name to the thread.

    
    import threading
    
    def my_function():
        print("Thread name:", threading.current_thread().getName())
    
    t = threading.Thread(target=my_function, name="MyThread")
    t.start()
    
    # Alternatively, you can use the setName() method
    t.setName("NewThreadName")
    print("Thread name:", t.getName())
    

    Yields the following output:

    
    Thread name: MyThread
    Thread name: NewThreadName
    

    <a id="h-5-2-query-thread-identifier"></a>

    5.2 Query Thread Identifier

    You can obtain the identifier of a thread by calling the ident attribute on the thread instance. This identifier is a non-negative integer that uniquely identifies the thread within the program.

    
    import threading
    
    def my_function():
        print(f"Identifier: {threading.get_ident()}")
    
    my_thread = threading.Thread(target=my_function)
    my_thread.start()
    

    <a id="h-6-what-are-daemon-threads"></a>

    6. What are Daemon Threads?

    A daemon thread is a thread that runs in the background and is killed automatically when the main program finishes. The daemon threads are useful for tasks that don't need to be completed before the program ends, such as background tasks that can be stopped at any time without causing any harm.

    Daemon threads are created using the setDaemon() method of the Thread class. When a thread is created, it inherits the daemon status of its parent thread by default.

    
    import threading
    import time
    
    def worker():
        print("Worker started")
        time.sleep(2)
        print("Worker finished")
    
    t = threading.Thread(target=worker)
    # Set the thread as a daemon
    t.setDaemon(True)   
    t.start()
    
    print("Main thread finished")
    
    # Output:
    # Worker started      
    # Main thread finished
    

    The daemon attribute can also be used to configure whether the thread is a daemon thread or not.

    
    my_thread.daemon = True
    print(my_thread.daemon)  
    
    # Output: True
    

    <a id="h-7-using-a-threadpoolexecutor"></a>

    7. Using a ThreadPoolExecutor

    The concurrent.futures module provides a ThreadPoolExecutor class . This class allows you to execute a large number of threads in a thread pool. The ThreadPoolExecutor can be used to simplify the management of multiple threads, by allowing you to submit multiple tasks to the executor and have them run in parallel on the thread pool.

    To use the ThreadPoolExecutor, you first create an instance of the class by specifying the maximum number of threads to use in the pool.

    
    import concurrent.futures
    
    # define a function to be run in parallel
    def task(number):
        print(f"Starting task {number}")
        result = number * 2
        print(f"Task {number} result: {result}")
        return result
    
    # create a thread pool with 4 threads
    executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
    
    # submit 10 tasks to the executor
    futures = [executor.submit(task, i) for i in range(10)]
    
    # wait for all tasks to complete
    concurrent.futures.wait(futures)
    
    # print the results of all tasks
    for future in futures:
        print(future.result())
    

    You can submit tasks to the executor using the submit() method, which returns a Future object that represents the execution of the task. You can use the result() method of the Future object to get the result of the task when it's done.

    <a id="h-8-summary-and-conclusion"></a>

    8. Summary and Conclusion

    We have covered the basics of threading in Python, including what threads are, how they differ from processes, and why they are useful. There are different ways to create and start threads, as well as how to query and configure their attributes. As always, if you have any questions or feedback, feel free to leave a comment below.

    Happy coding!

    <a id="h-related-articles"></a>

    Related Articles