多线程编程同步与互斥:第一章 threading模块入门
各位同学,今天我们来聊聊Python多线程编程的起点——threading模块。说实话,我刚接触多线程那会儿,也觉得这东西挺玄乎的。但用多了你会发现,它其实就是一套管理并发任务的工具集。咱们一步步来。
1. 创建线程的两种方式
Python里创建线程,说白了就两条路:直接传函数,或者继承Thread类。两种我都用过,各有各的适用场景。
方式一:通过函数创建线程
这是最直接的方式。你定义一个普通函数,然后把它丢给Thread对象就行。我个人习惯用这种方式处理简单的后台任务。
import threading
import time
def worker(name):
print(f"线程 {name} 开始工作")
time.sleep(2)
print(f"线程 {name} 工作结束")
# 创建线程
t1 = threading.Thread(target=worker, args=("A",))
t2 = threading.Thread(target=worker, args=("B",))
# 启动线程
t1.start()
t2.start()
你看,代码很简洁。target就是你要执行的函数,args是传给函数的参数。嗯,这里要注意,args必须是个元组,哪怕只有一个参数也要加个逗号。
方式二:通过继承Thread类创建线程
另一种方式,是写一个类继承threading.Thread,然后重写run方法。我在项目中遇到过需要在线程里维护状态的情况,这时候用类的方式就特别顺手。
import threading
import time
class MyWorker(threading.Thread):
def __init__(self, name):
super().__init__()
self.name = name
self.result = None # 可以保存线程执行结果
def run(self):
print(f"线程 {self.name} 开始工作")
time.sleep(2)
self.result = f"{self.name} 完成"
print(f"线程 {self.name} 工作结束")
# 创建并启动线程
t = MyWorker("C")
t.start()
t.join()
print(t.result) # 获取线程执行结果
你想想看,如果只是跑一个简单任务,用函数方式就够了。但如果你需要在线程里保存数据、或者要复用线程逻辑,那继承类的方式会更清晰。
两种方式对比:
| 对比项 | 函数方式 | 类方式 |
|---|---|---|
| 代码量 | 少,简洁 | 稍多,但结构清晰 |
| 适用场景 | 简单任务、一次性执行 | 需要保存状态、复杂逻辑 |
| 可扩展性 | 一般 | 强,可以加属性和方法 |
2. 线程的启动与等待
创建完线程,下一步就是让它跑起来。这里有两个关键方法:start() 和 join()。
start() 是告诉Python:「这个线程可以开始执行了」。注意,不是调用run()方法。我曾经见过新手直接调run(),结果线程根本没启动,代码是同步执行的。为什么会这样?因为run()只是普通方法调用,start()才会真正创建新线程。
join() 的作用是等待线程结束。说白了,就是让主线程停下来,等子线程干完活再继续。
import threading
import time
def slow_task(name, delay):
print(f"{name} 开始,需要 {delay} 秒")
time.sleep(delay)
print(f"{name} 结束")
# 创建三个线程
threads = []
for i in range(3):
t = threading.Thread(target=slow_task, args=(f"任务-{i}", i+1))
threads.append(t)
t.start()
# 等待所有线程完成
for t in threads:
t.join()
print("所有任务都完成了,主线程继续执行")
这段代码里,主线程会等三个子线程全部跑完,才打印最后那句话。如果不加join(),主线程可能一下子就跑完了,子线程还在后台慢慢执行。
小技巧: 如果你不想无限等待,可以给join()传一个超时参数,比如t.join(2)表示最多等2秒。超时后主线程继续往下走,不管子线程有没有结束。
3. 线程的daemon属性
daemon属性,中文叫「守护线程」。这个概念一开始我也觉得绕,其实理解起来很简单:守护线程就是主线程的跟班。
默认情况下,线程是非守护的。这意味着主线程会等所有非守护线程结束才退出。但如果你把某个线程设为守护线程,主线程退出时,这个线程会被强制终止,不管它有没有执行完。
import threading
import time
def background_work():
while True:
print("后台任务正在运行...")
time.sleep(1)
# 创建守护线程
daemon_thread = threading.Thread(target=background_work)
daemon_thread.daemon = True # 设置为守护线程
daemon_thread.start()
time.sleep(3)
print("主线程结束,守护线程会被强制终止")
运行这段代码,你会发现程序只跑3秒就结束了。那个while True的循环,因为被设为守护线程,主线程一退出它就被干掉了。
我曾经踩过的坑: 有一次我写了个日志收集的后台线程,忘记设daemon属性了。结果主程序退出后,那个线程还在跑,导致进程一直不退出。排查了半天才发现是这个问题。所以,如果你希望线程随主线程一起退出,记得设daemon=True。
关于daemon线程,有几点要记住:
- 必须在
start()之前设置daemon属性,否则会报错 - 守护线程里不要做重要的资源清理工作,因为它可能随时被强制终止
- 如果所有非守护线程都结束了,守护线程也会自动被终止
知识体系总览
下面这张图,我把本章的核心知识点串起来了。你可以把它当作一个快速参考。
好了,这一章的内容就这些。创建线程、启动等待、daemon属性,这三个点搞明白了,threading模块的基础就算打牢了。下一章我们会深入线程同步的问题——到时候你会遇到真正的挑战。