Creating Threads
Python's primary threading module is threading.
threading.current_thread(): Retrieves the currently active thread.- Thread objects have attributes like
nameandidentfor identification. - Threads can be created using functions or by extending a class.
Function-Based Thread Creation
Pass the target function to threading.Thread().
import threading
import os
import time
def task_executor(delay):
begin_time = time.perf_counter()
current = threading.current_thread()
print(f'{current.name} initiating...')
time.sleep(delay)
print(f'Thread: {current.name}, ID: {current.ident}, Process: {os.getpid()}')
print(f'{current.name} finished after {time.perf_counter() - begin_time:.2f} seconds')
program_start = time.perf_counter()
active_threads = []
for idx in range(1, 4):
thread = threading.Thread(target=task_executor, name=f'Worker-{idx}', args=(idx,))
thread.start()
active_threads.append(thread)
main_thread = threading.current_thread()
print(f'Main thread: {main_thread.name}, ID: {main_thread.ident}, Process: {os.getpid()}')
print(f"Total elapsed: {time.perf_counter() - program_start:.2f} seconds")
The output demonstrates that the main thread completes while child threads are still executing.
Class-Based Thread Creation
Extend the Thread class and override the run method.
import threading
import os
import time
class CustomThread(threading.Thread):
def __init__(self, delay, name=None):
super().__init__()
self.name = name
self.delay = delay
def run(self):
start = time.perf_counter()
current = threading.current_thread()
print(f'{current.name} starting...')
time.sleep(self.delay)
print(f'Thread: {current.name}, ID: {current.ident}, Process: {os.getpid()}')
print(f'{current.name} completed in {time.perf_counter() - start:.2f} seconds')
program_start = time.perf_counter()
for i in range(1, 4):
t = CustomThread(name=f'Task-{i}', delay=i)
t.start()
main = threading.current_thread()
print(f'Main: {main.name}, ID: {main.ident}, Process: {os.getpid()}')
print(f"Total time: {time.perf_counter() - program_start:.2f} seconds")
Thread Control Mechanisms
Daemon Threads
Setting daemon=True makes threads terminate when the main thread ends.
import threading
import time
def background_task(duration):
print(f'{threading.current_thread().name} running for {duration}s')
time.sleep(duration)
print(f'{threading.current_thread().name} done')
for i in range(3):
t = threading.Thread(target=background_task, name=f'Daemon-{i}', args=(i+1,))
t.daemon = True
t.start()
time.sleep(1.5)
print('Main thread exiting')
Thread Joining
The join() method blocks the main thread until the target thread completes.
import threading
import time
def delayed_operation(seconds):
print(f'Operation starting, will take {seconds}s')
time.sleep(seconds)
print('Operation complete')
thread_list = []
for i in range(3):
t = threading.Thread(target=delayed_operation, args=(i+1,))
t.start()
thread_list.append(t)
for thread in thread_list:
thread.join()
print('All threads have finished')
Synchronization Primitives
Basic Lock Mechanism
Shared resource access requires locking to prevent race conditions.
import threading
import time
import random
account_balance = 1000
def withdraw_funds(amount, lock_obj):
global account_balance
lock_obj.acquire()
current = account_balance
time.sleep(random.uniform(0, 0.1))
current -= amount
account_balance = current
print(f'Withdrew {amount}, remaining: {account_balance}')
lock_obj.release()
lock = threading.Lock()
threads = []
for i in range(5):
t = threading.Thread(target=withdraw_funds, args=(100, lock))
threads.append(t)
t.start()
for t in threads:
t.join()
print(f'Final balance: {account_balance}')
Reentrant Locks (RLock)
RLock allows a thread to acquire the same lock multiple times.
import threading
lock = threading.RLock()
def nested_lock_demo():
lock.acquire()
print('First lock acquired')
lock.acquire()
print('Second lock acquired')
lock.release()
print('First release')
lock.release()
print('Second release')
t1 = threading.Thread(target=nested_lock_demo)
t2 = threading.Thread(target=nested_lock_demo)
t1.start()
t2.start()
t1.join()
t2.join()
Thread Communication
Condition Variables
Condition objects enable thread coordination through wait/notify patterns.
import threading
import time
buffer = []
max_size = 5
condition = threading.Condition()
def producer():
global buffer
count = 0
while True:
with condition:
while len(buffer) >= max_size:
condition.wait()
item = f'Item-{count}'
buffer.append(item)
print(f'Produced: {item}')
count += 1
condition.notify()
time.sleep(0.5)
def consumer():
global buffer
while True:
with condition:
while not buffer:
condition.wait()
item = buffer.pop(0)
print(f'Consumed: {item}')
condition.notify()
time.sleep(1)
p = threading.Thread(target=producer)
c = threading.Thread(target=consumer)
p.daemon = True
c.daemon = True
p.start()
c.start()
time.sleep(3)
Semaphores
Semaphores limit concurrent access to resources.
from threading import Thread, Semaphore
import time
MAX_CONCURRENT = 3
resource_sem = Semaphore(MAX_CONCURRENT)
def access_resource(thread_id):
with resource_sem:
print(f'Thread {thread_id} accessing resource')
time.sleep(2)
print(f'Thread {thread_id} releasing resource')
threads = [Thread(target=access_resource, args=(i,)) for i in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
Event Objects
Events provide simple signaling betweeen threads.
from threading import Thread, Event
import time
signal = Event()
def signal_waiter(waiter_id):
print(f'Waiter {waiter_id} waiting')
signal.wait()
print(f'Waiter {waiter_id} proceeding')
def signal_setter():
time.sleep(2)
print('Setting signal')
signal.set()
Thread(target=signal_setter).start()
for i in range(3):
Thread(target=signal_waiter, args=(i,)).start()
Thread Pool Execution
The concurrent.futures module provides high-level thread pooling.
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
def process_item(item_id):
time.sleep(item_id * 0.5)
return f'Processed-{item_id}'
with ThreadPoolExecutor(max_workers=4) as executor:
futures = [executor.submit(process_item, i) for i in range(8)]
for future in as_completed(futures):
print(future.result())
Additional pool methods include map() for ordered results and wait() for synchronization.