## 😲`# %%`での Thread を掘り下げた
下面这段代码直接执行,和通过`# %%`看到的结果不一样。
```python
# %%
from threading import Thread
from time import sleep
print(f"Python version: {sys.version}")
def printer():
for i in range(3):
print(i, end=' ')
sleep(0.1)
thr = Thread(target=printer, daemon=True)
thr.start()
print() # Add newline
```
## daemon とは?
> デーモン〖daimōn〗
> 1ギリシャ神話で、半神半人。守護神。ダイモン。
> 2(daemon)コンピュータープログラムの一。メモリーに常駐し、オペレーティングシステムやアプリケーションソフトの動作状況に応じ、ファイルの管理やメールの送受信、印刷などの基本的なプロセスをバックグラウンドで実行する。
### Python のバージョン
![[1772853186419.webp]]
Python 3.14 の「free threading」 かな?
## 結論:「free threading」と関係ない、`thr.join()`がおすすめ
应该就是执行方式导致的进程参与。
![[2026-03-07_16-57-10.webp]]
## 🎯Python 3 エンジニア認定実践試験のため、Threadの再入門
[[Python実践レシ.pdf#page=465|Python実践レシ, Chapter 19 並行処理、並列処理]]
短答:不需要“深究到非常底层”,但需要“会用且知道边界”。作为 Python Web 工程师,把并发/并行的核心工具与实践吃透,能显著提升你在生产环境的可靠性与性能。
你应该掌握到什么程度(建议清单)
- 必备(高优先级)
- concurrent.futures 的用法:ThreadPoolExecutor、ProcessPoolExecutor、submit/map/as_completed、result/timeout、异常传播、shutdown。
- 线程与守护线程基础:Thread、start/join、daemon 的语义和影响(你已经在练习中遇到)。
- I/O 密集型与 CPU 密集型任务的选择:Web 场景常见 I/O(HTTP 调用、数据库、缓存、文件)→ 线程/异步更合适;CPU 重的任务→ 多进程或交给任务队列/专用服务。
- asyncio 基础:事件循环、await、async/await、to_thread/run_in_executor,把阻塞操作迁移到线程池。
- 生产中的超时/取消:超时参数、wait/as_completed、请求级别上下文取消。
- 实用(中优先级)
- 连接池与限流:数据库/HTTP 客户端池化、max_workers 与队列背压,避免把下游“打爆”。
- 常见并发原语:Lock/Semaphore/Event/Queue;避免共享可变状态,优先消息传递。
- 部署模型理解:WSGI/ASGI、Gunicorn/Uvicorn 的 workers 与线程数、协程并发度设置。
- 错误与资源回收:在回调/线程里抛出的异常如何记录;with 语句管理池与会话。
- 了解即可(低优先级,按需深入)
- Free-threaded(无 GIL)模式的现状与差异:知道它不是默认、何时可能带来 CPU 并行优势、库适配问题。
- 细粒度性能调优:profiling、队列无锁结构、批处理/零拷贝等。等你遇到性能瓶颈再下潜。
给你的练习文件的几点改进建议(贴近 Web 实战)
- 把“演示代码”封装成可测试的函数,加入 if __name__ == "__main__": 保护,便于导入与单测。
- 用 ThreadPoolExecutor 替代裸 Thread 管理“短任务”,with 块结束前会等待完成,更接近服务里“托管并发”的做法。
- 对打印/输出,生产里相当于 logging:加上 flush 或统一用 logging 模块,设置好级别/格式/handler。
- 对外部 I/O(HTTP/DB)统一设置超时与重试策略;对于批量任务,优先 as_completed 以尽快消费完成的结果。
一条可复用的小模板(线程池 + 超时 + 错误汇报)
```python
# Python
from concurrent.futures import ThreadPoolExecutor, as_completed
from typing import Iterable, Any
def do_io(item: Any) -> Any:
# TODO: 实际 I/O 调用,务必设置超时
return item
def run_batch(items: Iterable[Any], max_workers: int = 16, timeout: float = 5.0):
results, errors = [], []
with ThreadPoolExecutor(max_workers=max_workers) as ex:
fut_map = {ex.submit(do_io, it): it for it in items}
for fut in as_completed(fut_map, timeout=None):
it = fut_map[fut]
try:
results.append(fut.result(timeout=timeout))
except Exception as exc:
errors.append((it, exc))
return results, errors
```
路线图(2~4 周搞定实用度80%)
- 第1周:读 concurrent.futures 官方文档 + 写3个小练习(线程池 I/O、进程池 CPU、as_completed 聚合)。
- 第2周:读 asyncio 基础 + 在现有脚本里用 to_thread 或 run_in_executor 迁移一个阻塞点。
- 第3周:把你的练习项目接入 logging、超时、错误处理模板;写 2 个单元测试覆盖并发路径。
- 第4周:了解部署(Gunicorn/Uvicorn 配置)、连接池和背压;在本机做一次并发压测,观察指标与瓶颈。
总结
- 不用钻研到实现细节,但要学会“选工具、控并发、设超时、管资源、拿结果、收错误”。这就是 Web 工程中并发的八成价值。剩下两成的“更深入”(比如 free-threaded 的性能与生态)在你遇到瓶颈或要做性能优化时再补即可。
深入说说 daemon=True(守护线程),并结合常见 Web 业务场景,帮你形成“什么时候用、什么时候绝对别用”的直觉。
核心定义
- 守护线程不会阻止进程退出。进程在只剩守护线程时会直接结束,不等待它们收尾。
- 非守护线程(默认)会在进程退出前被等待完成,或者你可以显式 join。
- 守护与否不影响线程能否并发执行,只影响“进程生命周期结束时”的等待策略。
常见误解澄清
- 守护线程不是“后台更快”或“更省资源”的线程;它只是“主线程结束时可被直接中断”的线程。
- 它不会自动安全关闭资源;被“砍”时没有 finally 兜底。
- 守护线程和 GIL/free-threaded 没关系;只是退出语义。
何时适合 daemon=True(少数场景)
- 辅助型、可丢弃的后台工作,且进程退出时不需要完整收尾:
- 示例:进程内的统计心跳、轻量级指标上报缓冲器(最好也有缓冲上限,且退出时丢数据可接受)。
- 示例:开发阶段的热重载文件监视器(真正退出时不在乎它是否优雅收尾)。
- 你明确在主线程(或非守护线程)里有一个“长期存活的前台循环”(如 REPL、服务主循环),只用守护线程来跑辅助任务,不打算在退出时等待它们。
强烈不建议 daemon=True(多数业务场景)
- 任何需要可靠性的工作:写数据库、落盘、发账单、扣费、发送重要通知、提交任务队列、对外回调等。
- 需要释放外部资源的工作:关闭网络连接、刷新缓冲、提交事务、写日志到文件/Socket 等。
- 需要确定性顺序的输出/日志。
Web 场景分解与建议
1) WSGI/ASGI Web 服务内的并发
- 常见需求:在处理请求时做额外 I/O(调用下游、读写缓存、并行拼装页面)。
- 建议:
- 若是同步框架:优先用 ThreadPoolExecutor(with 管理)或显式 Thread + join,确保在请求生命周期内收集并合并结果。绝不使用 daemon=True 逃避等待。
- 若是异步框架(FastAPI/Starlette/Aiohttp):并行用 asyncio(gather/TaskGroup),或把阻塞任务 to_thread/run_in_executor。请求返回前 await 结果,或把可延迟的事丢到可靠队列(见下条)。
2) 请求后置任务(发送邮件、生成报表、图像转码)
- 需求:不要阻塞请求,但必须最终完成。
- 反模式:在请求处理函数里起个 daemon 线程“后台做”。进程重启/扩缩容时,任务会直接蒸发。
- 正确姿势:
- 可靠任务队列:Celery/RQ/Huey/Arq,或云服务队列/事件总线(SQS/PubSub/Kafka),在单独 worker 进程执行。
- 若一定要本进程做,至少用非守护线程并持久化任务进度,或使用 ThreadPoolExecutor 的受控生命周期(仍不理想,抗宕性差)。
3) 后台周期任务(心跳、缓存预热、定时同步)
- 需求:服务存活期间周期执行,优雅退出时尽量停干净。
- 建议:
- 用调度器(APScheduler)或你自己的“前台守护”线程,在线程内有可中断循环 + 监听停止事件(threading.Event)。
- 线程不要设为 daemon;在进程停机时发停止信号并 join,释放资源。
- 示例框架:
```python
# Python
import threading
import time
stop = threading.Event()
def background_job():
while not stop.is_set():
try:
# do work
time.sleep(5)
except Exception as e:
# 记录日志,避免线程静默死亡
pass
t = threading.Thread(target=background_job) # 非守护
t.start()
# 优雅退出时:
stop.set()
t.join(timeout=10)
```
- 如果你把它设为 daemon=True,停机时就没有“最后一轮 flush/提交/清理”的机会。
4) 日志与遥测
- 需求:尽量不丢,尤其是错误日志和关键指标。
- 反模式:异步日志 worker 用 daemon=True,停机就丢尾部日志。
- 建议:
- 用 logging.handlers.QueueHandler + QueueListener(监听线程可非守护),停机时 listener.stop() 并 join。
- 或用成熟的异步日志库/agent,支持 flush/关停协议。
5) 爬虫/批处理/ETL 脚本
- 需求:并发抓取/处理,任务必须完整、可重试。
- 反模式:用 daemon=True 开几十个后台线程,主线程异常时“随缘退出”,中途结果丢失。
- 建议:
- 用 ThreadPoolExecutor / ProcessPoolExecutor;异常自动汇报到 Future,with 结束前等待所有任务完成或取消。
- 把进度持久化(断点续跑)。
实践要点与代码模版
A. 短任务并发(请求内汇聚)
```python
# Python
from concurrent.futures import ThreadPoolExecutor, as_completed
import requests
def fetch(url, timeout=2):
r = requests.get(url, timeout=timeout)
r.raise_for_status()
return r.json()
def gather(urls, max_workers=8, per_req_timeout=2, overall_timeout=3):
results, errors = [], []
with ThreadPoolExecutor(max_workers=max_workers) as ex:
futs = [ex.submit(fetch, u, per_req_timeout) for u in urls]
for fut in as_completed(futs, timeout=overall_timeout):
try:
results.append(fut.result())
except Exception as e:
errors.append(e)
return results, errors
```
- 不用 daemon;with 块保证任务收尾确定。
B. 后台周期任务(可控停机)
```python
# Python
import threading
import time
import logging
log = logging.getLogger(__name__)
stop = threading.Event()
def metrics_pusher():
while not stop.is_set():
try:
# 推送一次指标
time.sleep(10)
except Exception:
log.exception("metrics push failed")
def start_metrics_thread():
t = threading.Thread(target=metrics_pusher, name="metrics-pusher") # 非守护
t.start()
return t
# 启动
t = start_metrics_thread()
# 优雅退出
stop.set()
t.join(timeout=5)
```
C. 如果你真的要 daemon=True
- 只用于“可丢弃”的辅助性工作,并明确写下注释:
```python
# Python
t = Thread(target=best_effort_watcher, daemon=True) # best-effort only; may be cut on exit
t.start()
```
- 不要在 daemon 线程里持有关键资源(数据库事务、文件句柄)或承诺交付的任务。
- 不要指望 finally/atexit 在 daemon 线程上能完成清理。
和你当前练习代码的对比与改进
- 你在练习里用 daemon=True 打印数字,这是一个很好的“现象教学”例子,但在业务代码里应该换成:
- 非守护 + join,或
- 用 ThreadPoolExecutor 包起来(with 自动等待)。
- 在 VS Code/PyCharm 的 cell 环境下,daemon 线程看似能跑完,是因为控制台进程没退出;脚本/生产环境不会这样。
速记总结
- 80% 的 Web 业务里,不需要 daemon=True。
- 用它的唯一合理理由:那项工作在进程退出时“可以被硬切”,且“丢失不影响正确性和合规”。
- 更好的默认:非守护线程 + 明确的生命周期管理(join、事件、with 语义),或上升到任务队列/调度器/池化执行器。
## 🧐Python に関してのホワイダニット
<https://github.com/chinesehuazhou/python-whydo> (中国語のみ)
> 「なぜ Python はこうなったのか」シリーズは、Python の構文、設計思想、発展の歴史、他言語との違いなどに焦点を当てた一連の記事(翻訳版を含む)です。「なぜ」という問いかけを起点に、Python の魅力をわかりやすく伝えようとしています。
>
> 多くの人が「どうやってできるか」(HOW-TO)や「何であるか」(WHAT-IS)に関心を持つため、このような記事は至る所に存在し、内容が画一化しがちです。しかし、私が特に注目しているのは「なぜこのなったのか」(WHY-DO)や「なぜそうなっていないのか」(WHY-NOT-DO)といったテーマです。これは異なる思考方法を反映しており、時には実務的な価値よりも哲学的・美学的な側面に傾くことがあります。
>
> 「なぜ」という問いに答えるのは容易ではありません。なぜなら、一つの「なぜ」に対する答えが新たな「なぜ」を生み、最終的な答えが哲学的・美学的な領域にまで及ぶことがあり、その結果「賛同する人は完全に同意し、反対する人は完全に否定する」という対立が生じるからです。