Python 线程如何创建和结束?daemon、join 有什么作用?
简化版
Python 可以用 threading.Thread 创建线程,调用 start() 后才真正启动,线程函数执行完就结束。join() 用来等待线程结束;daemon=True 表示守护线程,主程序退出时不会等待它完成,适合后台辅助任务,不适合承载必须完成的数据写入。
详细版
基本写法:
import threading
def worker(name):
print(f"hello {name}")
t = threading.Thread(target=worker, args=("A",))
t.start()
t.join()
关键点:
Thread(...)只是创建线程对象;start()才会启动新线程;- 不要直接调用
run(),否则只是普通函数调用; join()会阻塞当前线程,直到目标线程结束;daemon=True的线程不会阻止进程退出。
守护线程示例:
t = threading.Thread(target=background_reporter, daemon=True)
t.start()
面试里要注意:守护线程可能在任务没做完时被强制结束,所以不要用它做关键数据写入、订单处理、必须清理的资源释放。
完整版教学
一、Thread 对象和真正线程不是一回事
创建 Thread 对象只是创建了一个 Python 对象:
t = threading.Thread(target=worker)
这时新线程还没有开始执行。只有调用:
t.start()
解释器才会创建并调度新的执行流。很多初学者犯的错误是直接调用:
t.run()
run() 只是当前线程里的普通方法调用,不会产生并发效果。
二、join 的本质是等待
如果主线程启动子线程后立刻继续往下走,可能主线程先执行结束。join() 用来等待线程完成:
t.start()
t.join()
print("worker finished")
也可以指定超时时间:
t.join(timeout=3)
if t.is_alive():
print("thread still running")
注意:join(timeout=3) 超时不会杀死线程,只是当前等待结束。Python 标准线程没有安全的“强制杀线程” API,设计线程时应该让它能主动退出。
三、daemon 线程适合什么场景
守护线程的意思是:当只剩守护线程时,Python 程序可以直接退出,不会等它们执行完。
适合场景:
- 定期上报状态;
- 后台打印心跳;
- 临时缓存清理;
- 不影响主逻辑的辅助任务。
不适合场景:
- 写数据库;
- 写关键文件;
- 处理支付、订单、消息确认;
- 必须释放外部资源的任务。
因为程序退出时,守护线程可能没有机会执行 finally 代码,也可能没有机会 flush 文件缓冲。
四、线程如何优雅结束
由于不能随便强杀线程,常见做法是用共享标志或 Event:
import threading
import time
stop_event = threading.Event()
def worker():
while not stop_event.is_set():
print("working")
time.sleep(1)
t = threading.Thread(target=worker)
t.start()
time.sleep(3)
stop_event.set()
t.join()
这样线程会在合适的位置自己退出,资源清理也更可靠。
五、异常在线程里怎么表现
线程函数里抛异常,不会自动抛回创建线程的地方。它通常会打印异常堆栈,然后该线程结束。主线程如果不做额外设计,可能并不知道业务任务失败了。
实际项目中可以:
- 在线程函数里捕获异常并记录;
- 使用
concurrent.futures.ThreadPoolExecutor,通过future.result()获取异常; - 设计结果队列,把成功和失败都返回主线程。
六、常见误区与追问
| 概念 | 作用 | 面试陷阱 |
|---|---|---|
start() | 真正启动新线程 | 不能对同一线程对象调用两次 |
run() | 线程要执行的函数入口 | 直接调用只是普通函数调用 |
join() | 等待线程结束 | 可设置超时,避免无限等待 |
daemon | 不阻止进程退出 | 可能来不及清理资源 |
- 误区:创建 Thread 对象就等于线程已经运行。 只有调用
start()后,解释器才会调度新线程执行目标函数。 - 误区:直接调用 run() 也能开启新线程。
run()直接调用只在当前线程同步执行,不会创建并发执行流。 - 误区:daemon 线程适合处理重要收尾逻辑。 守护线程不会阻止进程退出,可能在写文件、刷日志、释放连接前被直接结束。
- 追问:join(timeout) 后怎么判断线程是否结束?
join(timeout)不返回是否成功,需要再用is_alive()判断线程是否仍在运行。 - 追问:Python 线程能被外部强制安全杀掉吗? 标准线程没有推荐的强杀机制,通常用
Event、队列哨兵或共享标志让线程协作退出。 - 追问:线程里的异常会不会自动抛到主线程? 不会自动让主线程捕获;需要在线程函数里记录异常,或使用
Future让调用方通过result()感知。
记忆钩子:
start才开线程,join只是等待,daemon只决定进程退出时是否等它。
七、加强记忆
线程生命周期抓四个词:创建、启动、等待、退出。Thread 只是对象,start() 才启动,join() 是等待,不是杀线程;daemon 是“主程序不用等我”,所以只适合可丢弃的后台辅助任务,关键业务要用非守护线程并设计优雅退出。