PYTHON

Threading


Threading is a way to run multiple threads (smaller units of process) concurrently in a single process space. Threads are lighter than processes and share the same memory space, which makes it easier and faster to communicate between threads than between processes. However, threading can be complex due to the Global Interpreter Lock (GIL) in CPython, which prevents multiple native threads from executing Python bytecodes at once.

 

Key Concepts in Threading

  1. Thread: The smallest unit of a process that can be scheduled and executed by the operating system.
  2. Concurrency: Running multiple threads in overlapping time periods.
  3. Parallelism: Running multiple threads simultaneously, which requires multiple CPUs or CPU cores.
  4. Global Interpreter Lock (GIL): A mutex that protects access to Python objects, preventing multiple threads from executing Python bytecodes simultaneously in CPython.

 

 

Creating and Running Threads

The threading module in Python provides a way to create and manage threads. Here are the basic steps to create and run a thread:

Example 1: Creating and Running a Simple Thread

import threading
def print_numbers():
   for i in range(1, 6):
       print(i)
# Create a thread
thread = threading.Thread(target=print_numbers)
# Start the thread
thread.start()
# Wait for the thread to finish
thread.join()
print("Thread finished execution")
 

 

Example 2: Using a Class to Create Threads

You can also create threads by subclassing threading.Thread and overriding the run method.

import threading
class PrintNumbersThread(threading.Thread):
   def run(self):
       for i in range(1, 6):
           print(i)
# Create and start the thread
thread = PrintNumbersThread()
thread.start()
# Wait for the thread to finish
thread.join()
print("Thread finished execution")

 

 

Thread synchronization

When multiple threads access shared data, synchronization mechanisms are needed to avoid data corruption. The threading module provides several synchronization primitives:

  1. Lock: A simple synchronization primitive that can be used to ensure that only one thread accesses a shared resource at a time.
lock = threading.Lock()
def thread_safe_function():
   with lock:
       # Critical section of code
       pass

 

2.RLock (Reentrant Lock): A lock that can be acquired multiple times by the same thread without causing a deadlock.

rlock = threading.RLock()
def reentrant_function():
   with rlock:
       # Critical section of code
       pass

 

3.Semaphore: A synchronization primitive that allows a fixed number of threads to access a resource.

semaphore = threading.Semaphore(3)
def limited_access_function():
   with semaphore:
       # Critical section of code
       pass

 

4.Event: A synchronization primitive that can be used to signal one or more threads to proceed

event = threading.Event()
def wait_for_event():
   event.wait()  # Block until the event is set
   print("Event occurred")
# Create and start a thread
thread = threading.Thread(target=wait_for_event)
thread.start()
# Trigger the event
event.set()

 

5.Condition: A synchronization primitive that can be used to wait for some condition to be met.

condition = threading.Condition()
def wait_for_condition():
   with condition:
       condition.wait()  # Block until notified
       print("Condition met")
def notify_condition():
   with condition:
       condition.notify_all()  # Notify all waiting threads
# Create and start threads
thread1 = threading.Thread(target=wait_for_condition)
thread2 = threading.Thread(target=notify_condition)
thread1.start()
thread2.start()

 


PYTHON