← 返回题目列表

Python 线程如何创建和结束?daemon、join 有什么作用?

高频 简单 第 1 / 27 题 更新于 2026/07/27
threadingThreaddaemonjoin

简化版

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 是“主程序不用等我”,所以只适合可丢弃的后台辅助任务,关键业务要用非守护线程并设计优雅退出。