Example 1: Basic Thread Creation
import threading
import time
def func01(number):
print("Function func01 start")
time.sleep(2)
print("Function func01 end")
def func02(number):
print("Function func02 start")
time.sleep(2)
print("Function func02 end")
if __name__ == '__main__':
thread1 = threading.Thread(target=func01, args=(1,))
thread2 = threading.Thread(target=func02, args=(2,))
start_time = time.time()
thread1.start()
thread2.start()
end_time = time.time()
print("Time elapsed:", end_time - start_time)
Daemon Threads
If a thread must run in an infinite loop and should not prevent the program from exiting, set it as a daemon thread. When only daemon threads remain, the Python program can exit gracefully.
Purpose: Daemon threads provide backrgound services, like the garbage collector.
Notes:
- Daemon threads terminate once all non-daemon threads have completed.
- The main thread finishes when all non-daemon thread are done, at which point daemon threads are reclaimed.
By default, child threads are non-daemon. Setting a thread as daemon allows the program to exit without waiting for it.
import threading
import time
def func01(number):
print("Function func01 start")
time.sleep(2)
print("Function func01 end")
def func02(number):
print("Function func02 start")
time.sleep(4) # Longer sleep to distinguish
print("Function func02 end")
if __name__ == '__main__':
thread1 = threading.Thread(target=func01, args=(1,))
thread2 = threading.Thread(target=func02, args=(2,))
start_time = time.time()
thread2.setDaemon(True) # Set daemon before start
thread1.start()
thread2.start()
end_time = time.time()
print("Time elapsed:", end_time - start_time)
Joining Threads (Blocking Main Thread)
Use join() to make the main thread wait for child threads to complete.
import threading
import time
def func01(number):
print("Function func01 start")
time.sleep(2)
print("Function func01 end")
def func02(number):
print("Function func02 start")
time.sleep(4)
print("Function func02 end")
if __name__ == '__main__':
thread1 = threading.Thread(target=func01, args=(1,))
thread2 = threading.Thread(target=func02, args=(2,))
start_time = time.time()
thread2.setDaemon(True)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
end_time = time.time()
print("Time elapsed:", end_time - start_time)
Thread Management with Classes
Inherit from threading.Thread and override the run method.
import threading
import time
class Student1(threading.Thread):
def __init__(self, name):
super().__init__(name=name)
def run(self):
print("Student1 start")
time.sleep(2)
print("Student1 end")
class Student2(threading.Thread):
def __init__(self, name):
super().__init__(name=name)
def run(self):
print("Student2 start")
time.sleep(2)
print("Student2 end")
if __name__ == '__main__':
thread1 = Student1("Alice")
thread2 = Student2("Bob")
start_time = time.time()
thread2.setDaemon(True)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
end_time = time.time()
print("Time elapsed:", end_time - start_time)
Inter-thread Communication
Using Shared Variables (Not Thread-safe)
import threading
import time
url_list = []
def fetch_detail(url_list):
while True:
if url_list:
url = url_list.pop()
print("Fetch detail start")
time.sleep(2)
print("Fetch detail end")
def fetch_list(url_list):
while True:
print("Fetch list start")
time.sleep(4)
for i in range(20):
url_list.append(f"http://example.com/{i}")
print("Fetch list end")
if __name__ == '__main__':
start_time = time.time()
list_thread = threading.Thread(target=fetch_list, args=(url_list,))
for _ in range(5):
detail_thread = threading.Thread(target=fetch_detail, args=(url_list,))
detail_thread.start()
list_thread.start()
end_time = time.time()
print("Time elapsed:", end_time - start_time)
Using Queue (Thread-safe)
import threading
import time
from queue import Queue
def fetch_detail(queue):
while True:
url = queue.get() # Blocks if queue is empty
print("Fetch detail start")
time.sleep(2)
print("Fetch detail end")
def fetch_list(queue):
while True:
print("Fetch list start")
time.sleep(4)
for i in range(20):
queue.put(f"http://example.com/{i}")
print("Fetch list end")
if __name__ == '__main__':
start_time = time.time()
url_queue = Queue(maxsize=1000)
list_thread = threading.Thread(target=fetch_list, args=(url_queue,))
for _ in range(5):
detail_thread = threading.Thread(target=fetch_detail, args=(url_queue,))
detail_thread.start()
list_thread.start()
url_queue.join()
end_time = time.time()
print("Time elapsed:", end_time - start_time)
Thread Synchronization
Using Lock
from threading import Lock, Thread
total = 0
lock = Lock()
def add():
global total
for _ in range(1000):
lock.acquire()
total += 1
lock.release()
def subtract():
global total
for _ in range(1000):
lock.acquire()
total -= 1
lock.release()
thread1 = Thread(target=add)
thread2 = Thread(target=subtract)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(total)
Using RLock (Reentrant Lock)
from threading import RLock, Thread
total = 0
lock = RLock()
def add():
global total
for _ in range(1000):
lock.acquire()
lock.acquire() # Nested acquire with RLock
total += 1
lock.release()
lock.release()
def subtract():
global total
for _ in range(1000):
lock.acquire()
total -= 1
lock.release()
thread1 = Thread(target=add)
thread2 = Thread(target=subtract)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(total)
Using Condition (Coordination)
import threading
class XiaoAi(threading.Thread):
def __init__(self, cond):
super().__init__(name="XiaoAi")
self.cond = cond
def run(self):
with self.cond:
self.cond.wait()
print(f"{self.name}: Here")
self.cond.notify()
self.cond.wait()
print(f"{self.name}: OK")
self.cond.notify()
self.cond.wait()
print(f"{self.name}: You live at the Yangtze River's end")
self.cond.notify()
self.cond.wait()
print(f"{self.name}: We both drink the same river water")
self.cond.notify()
self.cond.wait()
print(f"{self.name}: When will this sorrow end?")
self.cond.notify()
self.cond.wait()
print(f"{self.name}: I will not fail your love")
self.cond.notify()
class TianMao(threading.Thread):
def __init__(self, cond):
super().__init__(name="TianMao")
self.cond = cond
def run(self):
with self.cond:
print(f"{self.name}: XiaoAi?")
self.cond.notify()
self.cond.wait()
print(f"{self.name}: Let's recite a poem")
self.cond.notify()
self.cond.wait()
print(f"{self.name}: I live at the Yangtze River's head")
self.cond.notify()
self.cond.wait()
print(f"{self.name}: I miss you every day but cannot see you")
self.cond.notify()
self.cond.wait()
print(f"{self.name}: When will this river cease?")
self.cond.notify()
self.cond.wait()
print(f"{self.name}: I desire your heart like mine")
self.cond.notify()
self.cond.wait()
print(f"{self.name}: Then I will not fail your affection")
self.cond.notify()
self.cond.wait()
if __name__ == '__main__':
cond = threading.Condition()
xiaoai = XiaoAi(cond)
tianmao = TianMao(cond)
# Start order matters
xiaoai.start()
tianmao.start()
Using Semaphore (Controlling Concurrency)
import threading
import time
class HtmlSpider(threading.Thread):
def __init__(self, url, sem):
super().__init__()
self.url = url
self.sem = sem
def run(self):
time.sleep(2)
print("Got HTML text success")
self.sem.release()
class UrlProducer(threading.Thread):
def __init__(self, sem):
super().__init__()
self.sem = sem
def run(self):
for i in range(20):
self.sem.acquire()
spider = HtmlSpider(f"http://example.com/{i}", self.sem)
spider.start()
if __name__ == '__main__':
sem = threading.Semaphore(3) # Limit concurrent threads to 3
producer = UrlProducer(sem)
producer.start()