python使用Thread的setDaemon启动后台线程

本文详细解释了多线程编程中前后台线程的区别及应用场景。通过实例对比,阐述了如何利用setDaemon方法来控制线程的行为,以及这种方式对主线程退出时的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

多线程编程当中, 线程的存在形态比较抽象. 通过前台线程\后台线程, 可以有效理解线程运行顺序.(复杂的多线程程序可以通过设置线程优先级实现)
后台线程与前台线程的直接区别是,
1)setDaemon(True): 当主线程退出时,后台线程随机退出;
2)setDaemon(False)(默认情况): 当主线程退出时,若前台线程还未结束,则等待所有线程结束,相当于在程序末尾加入join().

实例:
例子描述:主线程调用giveures给出字符串s的md5摘要,同时在giveures当中启动一个线程打印字符串内容.

1.前台线程

import time
from hashlib import md5
from threading import Thread

def pmd(md):
    time.sleep(3) #使用sleep使得该线程比主线程晚结束
    print("backend recording:",md)


def giveures(s):
    md = md5(s.encode('utf-8'))
    res = md.digest()
    t = Thread(target=pmd,args=(s,))
    #t.setDaemon(True) 默认情况:t.setDaemon(False)
    t.start()
    return res

s = 'chrisyang'
res = giveures(s)
print(res)
exit()

运行结果:

b'h#\x86|\xa0\xeff\xc7u\xba\x18\xb2\xd2s\xf9\x9e'
backend recording: chrisyang

第一行打印出来之后,隔3s后打印出第二行,说明在等待pmd线程结束后进程才退出.

2.后台线程

import time
from hashlib import md5
from threading import Thread

def pmd(md):
    time.sleep(3) #使用sleep使得该线程比主线程晚结束
    print("backend recording:",md)


def giveures(s):
    md = md5(s.encode('utf-8'))
    res = md.digest()
    t = Thread(target=pmd,args=(s,))
    t.setDaemon(True)
    t.start()
    return res

s = 'chrisyang'
res = giveures(s)
print(res)
exit()

运行结果:

b'h#\x86|\xa0\xeff\xc7u\xba\x18\xb2\xd2s\xf9\x9e'

pmd线程因为sleep挂起一段时间,因此在主线程完成时还未结束就被强制退出了.

知识归纳:

以上的是前后台线程的外在区别,至于内在区别,转自Stack Overflow的一个精炼回答:
后台线程的责任是为整个主线程提供服务,如保持网络连接(发送keep-alive心跳包),负责内存管理与垃圾回收(实际上JVM就是这样做的). 因此这些线程与实际提供应用服务的线程有了逻辑上的”前/后”的概念,而如果主线程已经退出,那么后台线程也没有存在的必要.
如果没有这一机制,那么我们在主线程完成之后,还必须逐个地检查后台线程,然后在主线程退出之前,逐个地关闭它们. 有了前后线程的区分, 我们只需要负责管理前台线程, 完成主要的逻辑处理之后退出即可.

原文:

Daemon Threads Explanation

Some threads do background tasks, like sending keepalive packets, or performing periodic garbage collection, or whatever. These are only useful when the main program is running, and it’s okay to kill them off once the other, non-daemon, threads have exited.

Without daemon threads, you’d have to keep track of them, and tell them to exit, before your program can completely quit. By setting them as daemon threads, you can let them run and forget about them, and when your program quits, any daemon threads are killed automatically.

<think>我们正在讨论Python中的守护线程(Daemon Thread)和setDaemon方法的作用。根据用户提供的引用内容,我们可以总结如下: 1. **守护线程的定义**:守护线程是一种在后台运行的线程,它的生命周期依赖于主线程。当所有非守护线程(包括主线程)结束时,无论守护线程是否执行完毕,程序都会退出,并且守护线程会被强制终止。 2. **setDaemon方法的作用**:通过调用线程对象的`setDaemon(True)`方法,可以将该线程设置为守护线程。这个方法必须在`start()`方法调用之前设置。 3. **为什么需要守护线程**: - 避免由于子线程的死循环或者长时间运行导致主线程无法退出(即程序无法结束)。 - 主线程退出时,不需要等待守护线程完成,这样可以确保程序能够及时退出,同时避免产生孤儿进程(orphan process)或者僵尸进程(zombie process)。 4. **与join()方法的区别**: - `join()`方法的作用是阻塞主线程,直到调用`join()`方法的线程执行完毕,才会继续执行主线程。 - `setDaemon(True)`则相反,它允许主线程在退出时直接终止守护线程,而不需要等待它们完成。 5. **默认行为**:如果不设置,线程默认是非守护线程(即`daemon`属性为False),此时主线程会等待所有子线程结束才退出。 6. **示例说明**:在引用[4]的示例中,主线程启动了两个子线程t1(守护线程)和t2(非守护线程)。当主线程执行完打印'Done'后,它等待非守护线程t2结束(因为t2是非守护线程,主线程需要等待它)。而守护线程t1在运行2秒后,如果主线程已经结束(但此时主线程在等待t2,所以主线程并未结束),那么t1会继续运行直到完成。但是,如果主线程已经结束(比如在t2结束后),那么即使t1还没有运行完(比如t1的sleep时间很长),它也会被强制终止。 因此,我们可以这样回答用户的问题: ### Python守护线程setDaemon方法的作用 在Python中,`setDaemon()`方法用于设置线程是否为守护线程(daemon thread)。其核心作用是**定义线程的生命周期与主线程的关系**。 #### 一、基本作用 1. **控制程序退出行为**: - 当所有**非守护线程**(包括主线程)结束时,程序会立即退出,同时强制终止所有仍在运行的**守护线程**。 - 若未设置(默认`False`),则主线程需等待该子线程结束才能退出。 2. **避免程序僵死**: - 防止因子线程无限循环(如监控任务)导致主线程无法退出的情况[^1][^2]。 #### 二、设置方式 ```python import threading def background_task(): while True: print("Daemon thread working...") # 创建线程 t = threading.Thread(target=background_task) t.setDaemon(True) # ★ 必须在start()前调用 t.start() # 主线程结束则守护线程自动终止 ``` > 注意:Python 3.3+ 推荐直接设置`daemon`属性:`t.daemon = True` #### 三、典型应用场景 1. **后台监控任务** 如心跳检测、日志刷新等无需阻止程序退出的任务 ```python def log_flusher(): while True: flush_logs_to_disk() time.sleep(60) daemon_logger = threading.Thread(target=log_flusher) daemon_logger.daemon = True # 程序退出时自动终止 daemon_logger.start() ``` 2. **非关键性辅助操作** 如缓存更新、临时文件清理等[^3] #### 四、与join()的对比 | 方法 | 作用 | 线程结束条件 | |---------------|------------------------------|--------------------------| | `setDaemon(True)` | 设为守护线程 | 主线程结束时强制终止 | | `join()` | 阻塞主线程直到本线程完成 | 线程函数执行完毕自然终止 | > 重要提示:守护线程中避免进行**资源持久化操作**(如文件写入),因强制终止可能导致数据丢失[^4]。 #### 五、示例验证 通过时间轴演示守护线程行为: ```python import threading import time def task(name, duration): print(f"{name} started") time.sleep(duration) print(f"{name} completed") # 守护线程可能无法执行到这里 if __name__ == "__main__": # 守护线程(3秒任务) d_thread = threading.Thread(target=task, args=("Daemon", 3)) d_thread.daemon = True d_thread.start() # 非守护线程(1秒任务) nd_thread = threading.Thread(target=task, args=("Non-Daemon", 1)) nd_thread.start() # 主线程立即结束 print("Main thread exits") ``` 运行结果: ``` Daemon started Non-Daemon started Main thread exits Non-Daemon completed # 仅非守护线程完成输出 ``` > 说明:守护线程的`completed`输出未执行,因为主线程在非守护线程结束后退出,守护线程被强制终止[^2][^4]。 #### 六、注意事项 1. **资源清理风险**:守护线程被强制终止时,其`finally`块和上下文管理器`__exit__`可能不会执行 2. **子线程限制**:守护线程启动的子线程会自动继承守护属性[^5] ### 总结 `setDaemon(True)`的核心价值在于:**将线程标记为可丢弃的后台任务,确保主程序能干净利落地退出**,适用于执行不重要且可中断的后台服务。 §§相关问题§§ 1. 如何在守护线程中安全地执行资源释放操作? 2. Python线程中join()与setDaemon(True)能否同时使用? 3. 守护线程与普通线程在资源访问上有无特殊限制? 4. 为什么说守护线程可能导致数据丢失?如何避免? [^1]: 避免子线程无限死循环导致程序无法退出 [^2]: 主线程结束会强制回收守护线程 [^3]: 适用于非关键性后台任务 [^4]: 守护线程可能被强制终止导致未完成操作 [^5]: 守护线程的子线程自动继承守护属性</think>### Python守护线程`setDaemon`方法详解 #### 一、核心作用 `setDaemon(True)`将线程标记为**守护线程**,其核心作用是:**当主线程结束时,无论守护线程是否执行完毕,都会强制终止守护线程并退出程序**。这是为了解决两类关键问题: 1. **避免程序僵死**:防止因子线程无限循环导致主线程无法退出 2. **清理资源**:主线程退出时自动回收所有守护线程,避免产生孤儿进程[^1][^2] #### 二、工作机制对比 | 设置方式 | 主线程行为 | 子线程行为 | 典型场景 | |-------------------|------------------------------|------------------------------|----------------------| | `setDaemon(False)` (默认) | 必须等待子线程结束 | 独立运行直到任务完成 | 需要完整执行的任务 | | `setDaemon(True)` | 结束时强制终止守护线程 | 随主线程退出而立即终止 | 后台辅助任务 | #### 三、关键特性 1. **设置时机** 必须在`start()`方法前调用,否则抛出`RuntimeError` ```python t = threading.Thread(target=task) t.setDaemon(True) # ✅ 正确位置 t.start() ``` 2. **资源回收** 主线程退出时,解释器会自动回收守护线程资源,无需手动清理[^3] 3. **执行中断** 守护线程被终止时: - 不会执行`finally`代码块 - 不会完成上下文管理器的`__exit__` - 可能造成资源未释放(如文件未关闭) #### 四、典型应用场景 1. **后台监控任务** ```python def monitor(): while True: # 无限循环 check_system_status() time.sleep(60) daemon_thread = threading.Thread(target=monitor) daemon_thread.setDaemon(True) # 确保程序可退出 daemon_thread.start() ``` 2. **非关键日志记录** ```python def log_writer(): with open("app.log", "a") as f: while True: f.write(generate_log() + "\n") time.sleep(5) # 主程序退出时自动终止日志写入 threading.Thread(target=log_writer, daemon=True).start() ``` 3. **心跳检测** ```python def heart_beat(): while not shutdown_event.is_set(): ping_server() time.sleep(10) # Python 3.3+ 简化写法 beat_thread = threading.Thread(target=heart_beat, daemon=True) beat_thread.start() ``` #### 五、与`join()`的对比 | 方法 | 作用 | 线程生命周期控制 | |-----------------|-----------------------------|-----------------------------| | `setDaemon(True)` | 主线程退出时强制终止子线程 | 主→子 (单向控制) | | `join()` | 阻塞主线程直到子线程完成 | 子→主 (反向阻塞) | > ⚠️ **重要提示**:守护线程中避免执行**关键资源操作**(如数据库提交),因其可能被强制终止导致数据不一致[^4]。 #### 六、代码示例解析 基于引用[4]的案例: ```python import threading import time def task(name, duration): print(f"{name} START: {time.ctime()}") time.sleep(duration) print(f"{name} END: {time.ctime()}") # 守护线程可能不会执行到此 # 创建守护线程(2秒任务) t1 = threading.Thread(target=task, args=("Daemon", 2)) t1.daemon = True # 等价于 setDaemon(True) # 创建普通线程(5秒任务) t2 = threading.Thread(target=task, args=("Normal", 5)) t1.start() t2.start() print("Main EXIT") ``` **执行结果**: ``` Daemon START: Thu Oct 26 10:00:00 2023 Normal START: Thu Oct 26 10:00:00 2023 Main EXIT Normal END: Thu Oct 26 10:00:05 2023 # 普通线程完成 ``` > 守护线程的`END`输出**不会显示**,因为主线程在打印`Main EXIT`后,等待普通线程结束即退出,守护线程被强制终止[^4]。 #### 七、注意事项 1. **资源安全**:守护线程中避免操作需要清理的资源(如文件句柄、网络连接) 2. **子线程继承**:守护线程创建的线程自动继承守护属性 3. **退出顺序**:主线程退出时,守护线程的终止顺序不确定 4. **替代方案**:Python 3.3+ 推荐直接设置`daemon`属性 ```python t = threading.Thread(daemon=True, target=func) ``` ### 总结 `setDaemon(True)`本质是**建立主线程与子线程的生命周期绑定关系**,适用于执行不重要且可丢弃的后台任务。其核心价值在于确保程序能及时退出,同时避免手动管理大量后台线程的复杂度[^1][^5]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值