Post thumbnail

Python Threading Cheat Sheet.

Basics of Threading

Threading allows concurrent execution of code in Python. It is particularly useful for I/O-bound tasks.

Importing the Threading Module
import threading
Creating and Starting Threads
  • Create a Thread:

def some_function():
    print("Hello from the thread!")

thread = threading.Thread(target=some_function)
  • Using Arguments:

def worker(arg1, arg2):
    print(f"arg1: {arg1}, arg2: {arg2}")

thread = threading.Thread(target=worker, args=(10, 20))
Waiting for Threads to Complete
  • Join Method:

The join method in Python's threading module is used to wait for a thread to complete its execution. It ensures that the main program (or the thread calling join) pauses until the thread it is waiting for terminates. This is useful for synchronizing threads and ensuring that the main program does not continue until all threads have finished their work.

import threading

def worker():
    print("Thread is working...")
    # Simulate a long-running task
    import time
    print("Thread finished working.")

# Create a thread
thread = threading.Thread(target=worker)
# Start the thread
# Wait for the thread to complete

print("Main program continues after thread has finished.")

Other ways:

thread.join(timeout=1.0)  # Wait for 1 second at most

Why Use join?

  • Synchronization: Ensures that the main program or parent thread waits for child threads to complete before proceeding, which is crucial for managing thread dependencies.

  • Resource Management: Helps avoid issues like premature termination of the main program or resource conflicts by ensuring all threads have finished their tasks.
Thread Synchronization
  • Locks vs. RLocks

Locks (threading.Lock) and Reentrant Locks (threading.RLock) are synchronization primitives in Python's threading module. Both are used to ensure that only one thread accesses a shared resource at a time, but they differ in their usage and behavior.


  • Usage: A Lock is a basic primitive used to protect a critical section, ensuring that only one thread can execute the section at a time.

  • Behavior:

    • A thread can acquire a Lock once.
    • If a thread attempts to acquire the same Lock again without releasing it, it will cause a deadlock.
import threading

lock = threading.Lock()

def critical_section():
    with lock:
        # Critical section code
        print("Lock acquired")

thread1 = threading.Thread(target=critical_section)
thread2 = threading.Thread(target=critical_section)




  • Usage: An RLock (Reentrant Lock) is used in situations where the same thread needs to acquire the lock multiple times without causing a deadlock.

  • Behavior:

    • A thread can acquire an RLock multiple times.
    • The thread must release the RLock the same number of times it has acquired it to fully release the lock.
import threading

rlock = threading.RLock()

def recursive_function(n):
    if n > 0:
        with rlock:
            print(f"RLock acquired, recursion level {n}")
            recursive_function(n - 1)

thread = threading.Thread(target=recursive_function, args=(3,))


Choosing Between Lock and RLock
  • Use Lock when:

    • You have simple locking requirements.
    • There is no need for the same thread to acquire the lock multiple times.
  • Use RLock when:

    • You need a thread to re-enter the lock without causing a deadlock.
    • You are working with recursive functions or complex control flows where the same lock might be acquired multiple times.
Thread Communication

Event :

An Event manages a flag that can be set, cleared, and waited upon. It's used for simple signaling between threads.

  • Setting an Event: Signals that an event has occurred.
  • Clearing an Event: Resets the event flag.
  • Waiting for an Event: Blocks until the event flag is set.
import threading

event = threading.Event()

def waiter():
    print("Waiting for event...")
    print("Event received!")

thread = threading.Thread(target=waiter)

# Trigger the event
import time
print("Setting event")

Condition :

A Condition variable allows threads to wait for certain conditions to be met. It combines a lock with methods for waiting and notifying.

  • wait(): Blocks until notified.
  • notify(): Wakes up one waiting thread.
  • notify_all(): Wakes up all waiting threads.
import threading

condition = threading.Condition()

def consumer():
    with condition:
        print("Consumer waiting...")
        print("Consumer consumed resource")

def producer():
    with condition:
        print("Producer making resource available")

consumer_thread = threading.Thread(target=consumer)
producer_thread = threading.Thread(target=producer)

import time


Semaphore :

A Semaphore limits the number of threads that can access a resource at the same time.

  • acquire(): Decrements the semaphore counter, blocking if the counter is zero.
  • release(): Increments the semaphore counter, allowing another thread to acquire it
import threading

semaphore = threading.Semaphore(2)

def access_resource():
    with semaphore:
        print("Resource accessed")
        import time

threads = []
for i in range(5):
    thread = threading.Thread(target=access_resource)

for thread in threads:
Use Cases
  • Event: Notifying worker threads of state changes or new data availability.
  • Condition: Synchronizing threads based on specific conditions, like a producer-consumer scenario.
  • Semaphore: Controlling access to a limited resource, such as a connection pool or file access.

And it will be enough to get through the Python threading hot topic. If you are also interested in data-structures in threading and how to line up threads make sure to check them out. 

Another more important topic that you can cover in 5 minutes reading is Daemon Threading , it will sor sure boost the knowledge on how to handles threading better.

And that's it you are good to go...

• • •

Latest Opinions

No Opinion so far...


No internet connection

Trying to reconnect...