## 😲`# %%`での 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)といったテーマです。これは異なる思考方法を反映しており、時には実務的な価値よりも哲学的・美学的な側面に傾くことがあります。 > > 「なぜ」という問いに答えるのは容易ではありません。なぜなら、一つの「なぜ」に対する答えが新たな「なぜ」を生み、最終的な答えが哲学的・美学的な領域にまで及ぶことがあり、その結果「賛同する人は完全に同意し、反対する人は完全に否定する」という対立が生じるからです。