惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
C
CERT Recently Published Vulnerability Notes
雷峰网
雷峰网
美团技术团队
L
LangChain Blog
Google DeepMind News
Google DeepMind News
博客园 - 【当耐特】
I
InfoQ
www.infosecurity-magazine.com
www.infosecurity-magazine.com
J
Java Code Geeks
B
Blog
博客园 - 三生石上(FineUI控件)
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Secure Thoughts
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园_首页
博客园 - Franky
Apple Machine Learning Research
Apple Machine Learning Research
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
GbyAI
GbyAI
TaoSecurity Blog
TaoSecurity Blog
N
Netflix TechBlog - Medium
H
Heimdal Security Blog
T
Troy Hunt's Blog
N
News and Events Feed by Topic
V2EX - 技术
V2EX - 技术
腾讯CDC
Forbes - Security
Forbes - Security
P
Privacy & Cybersecurity Law Blog
I
Intezer
Hacker News - Newest:
Hacker News - Newest: "LLM"
Y
Y Combinator Blog
The Register - Security
The Register - Security
Martin Fowler
Martin Fowler
Hugging Face - Blog
Hugging Face - Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Blog — PlanetScale
Blog — PlanetScale
L
Lohrmann on Cybersecurity
Security Latest
Security Latest
AWS News Blog
AWS News Blog
Scott Helme
Scott Helme
Webroot Blog
Webroot Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
MongoDB | Blog
MongoDB | Blog
Vercel News
Vercel News
Engineering at Meta
Engineering at Meta
大猫的无限游戏
大猫的无限游戏
A
Arctic Wolf
S
Security Affairs
P
Privacy International News Feed

暗无天日

读:AI Agent 安全日志——从可见性与隐私的两难说起 - 暗无天日 AI写作的语言指纹——如何让文字不那么像机器 - 暗无天日 读:50 条 Claude Code 技巧——一个工程经理的六个月使用心得 读:AI 辅助开发为什么让 E2E 测试更有价值 - 暗无天日 读:在Emacs中使用Claude Code(Spacemacs适配版) - 暗无天日 Claude Code 背后的工程哲学——读 Agent Harness Engineering 读:Agent Harness Engineering——AI 智能体不只是模型,还有套件 - 暗无天日 browser-harness:让 AI 直接接管你的浏览器 - 暗无天日 读:Security-First CI/CD —— DevSecOps 自动化实践指南 TIL: 数字小键盘的小数点陷阱与行内算术求值 - 暗无天日 读:Immutability 不是万能药,它是一种权衡 - 暗无天日 Conducty:给 Claude Code 加上项目记忆和并行执行能力 - 暗无天日 读 — GitHub Trending 里的 Claude Code 技能包 读 — Prompt Caching 省钱指南 TIL: Emacs 中那些跟鼠标配合的冷门快捷键 - 暗无天日 读:Anvil——把 Emacs 变成 AI 的工具服务器 读:Emacs 代码折叠终极指南 - 暗无天日 读:Clojure 搭车客指南 - 暗无天日 git推送失败后恢复仓库损坏的完整记录 - 暗无天日 多智能体系统的两个有效模式——以及对 Claude Code 用户的启示 - 暗无天日 用 Org Babel 写 Literate 博文:扩展执行 + 定制导出 proced:Emacs 内置的进程查看器 - 暗无天日 从 proced 定制中学到的 Elisp 模式 读:让 Emacs proced 在 macOS 上显示 CPU 和内存 异步编程的函数着色税 - 暗无天日 链式调用的代价:JavaScript 和 Clojure 的共同教训 - 暗无天日 hyperfine:命令行基准测试工具 - 暗无天日 管道中的变量去哪了?——子 shell 作用域陷阱 - 暗无天日 开源包装器的信任陷阱:四个危险信号 - 暗无天日 程序员愿意为 AI 写文档,却不愿为同事写 - 暗无天日 mktemp: Shell 脚本中临时文件的安全陷阱与最佳实践 - 暗无天日 WSL9x —— 在 Windows 9x 里跑 Linux 内核 6.19 用 ox.el 做你想做的事 —— org-export 高级编程指南 读:Hot-wiring the Lisp Machine —— 用纯 Elisp 构建零依赖的 Org 静态站点生成器 Elisp 性能优化的六个实战教训 - 暗无天日 fcitx5 下 Emacs 无法切换输入法的排查 - 暗无天日 ERT 测试交互命令的三种方式 - 暗无天日 SEM Assistant: 当 Elisp 守护进程遇上 LLM 用 dmsg 给 Elisp 加上结构化调试日志 用 org-habit 追踪非每日习惯 - 暗无天日 Clojure X-Men:当编程语言特性变成超能力 - 暗无天日 TIL: 用 diff-hl 在 fringe 中显示 git 变更 读:llm-test —— 用 LLM agent 驱动 Emacs 测试 TIL: AI 时代的橡皮鸭调试 - 暗无天日 fcitx 启动后键盘输入卡顿的排查 - 暗无天日 TIL: 早期网页的图片热区导航 - 暗无天日 读 Seeing the Whole System 用 Emacs 自动生成每周链接推荐 - 暗无天日 读:ASCII control characters in my terminal 读 What to learn - 暗无天日 Lisp 的括号之痛——一个愚人节玩笑揭开的老伤疤 - 暗无天日 一本书该"线性读"还是"并行读" - 暗无天日 读 How to Monetize a Blog:一篇伪装成变现指南的讽刺文 Python Mock 第三方依赖的四种策略 - 暗无天日 Emacs Lisp 热重载实用指南 - 暗无天日 Prot 的 Emacs 配置哲学 - 暗无天日 TIL: 从直播对谈中学到的三个 Emacs 技巧 - 暗无天日 TIL: 自动使用项目虚拟环境的 Python - 暗无天日 TIL: 让 Help buffer 自动获得焦点 一条命令让本地开发用上 HTTPS —— slim 工具介绍 用 fsck 检查和修复 Linux 文件系统 排查Linux进程"卡死"实战:从strace到gdb全流程 - 暗无天日 PostgreSQL 索引:从基础到你可能不知道的高级用法 - 暗无天日 用 .pdbrc 自定义 Python 调试器 ANSI 转义码的标准化现状 - 暗无天日 终端程序的潜规则 - 暗无天日 PARA Org-mode 测试配置 - 暗无天日 AI越强越辣鸡?控制论说这是必然的 - 暗无天日 AI 越强越需要你盯着——反馈循环实操指南 - 暗无天日 你的AI代理正在偷你的密钥——四种你没想到的泄露通道 - 暗无天日 LLM 在 DevOps 中的三种角色 - 暗无天日 写作风格的反建议 - 暗无天日 反驳本质复杂性——Dan Luu 论为什么《没有银弹》错了 - 暗无天日 文件充满了危险——Dan Luu 谈文件系统的可靠性陷阱 - 暗无天日 AI 时代的 PARA 方法:用 Org-mode 和 AI 打造个人知识管理系统 Linux 数据去重学习笔记 - 暗无天日 创建跨平台 ZIP 文件的隐藏陷阱:Extra Field - 暗无天日 X11 Forwarding 排障指南 - 暗无天日 IP欺骗端口扫描:当别人冒充你去扫描别人 - 暗无天日 Linux 输入栈全景解析:从硬件按键到屏幕响应 - 暗无天日 Unix 系统中那些被埋没的配置开关——以 FontConfig 为例 - 暗无天日 在Linux上限制儿童使用电脑 - 暗无天日 GIF不仅仅是一种图片格式——用GIF流做些奇怪的事 - 暗无天日 Leiningen 学习笔记:Clojure 项目构建与管理从入门到实战配置 - 暗无天日 Google SRE Book 读书笔记 - 暗无天日 yes 管道 head 发生了什么 - 暗无天日 为什么 nohup 在 crontab 中不起作用 Bash中的Indirection与Nameref - 暗无天日 Linux PAM 简介 - 暗无天日 从Linux ISO文件启动计算机 - 暗无天日 用 Bash 打造一个Screen Locker 用GitHub Actions自动构建EGO博客 - 暗无天日 blocking I/O 的作用 - 暗无天日 mobileog 手机端同步提示Error:2 No such file 的解决方法 回收 WSL2 VHDX 文件占用空间 使用 org-mode columnview 生成任务列表 - 暗无天日 Emacs 作为 MPD 客户端 - 暗无天日 移动文件路径却不破坏org file link的方法 - 暗无天日 如何合理的导出help link 成HTML - 暗无天日 笑话理解之Biology - 暗无天日
读:Python 延迟——time.sleep() 不是万能的 - 暗无天日
lujun9972,Claude Code · 2026-06-05 · via 暗无天日

Real Python 有一篇教程讲了 Python 中添加延迟的多种方法。time.sleep() 这东西在普通脚本里用着挺顺手,但程序一旦涉及多线程、异步或者 GUI,它就开始给你添乱了。线程退不掉、事件循环冻死、界面卡住,根子都在一件事上,time.sleep() 阻塞的是整个线程,而你的程序可能在一个线程里同时处理好几件事。

普通脚本,time.sleep() 够用

基本用法

time.sleep() 接受一个秒数参数(支持小数),暂停当前线程。

import time

print("开始")
time.sleep(3)
print("结束")

有两点要留心。一是 time.sleep() 暂停的是 调用它的线程 ,不是整个进程,如果你的程序只有主线程,那就是暂停整个程序。二是精度不保证,你传 3 秒,实际等待几乎总是略长于 3 秒,因为操作系统调度有开销。

精度到底差多少

用 time.perf_counter() 可以精确测量。连跑 5 次 sleep(1),看看实际耗时是多少。

import time

for i in range(5):
    start = time.perf_counter()
    time.sleep(1)
    elapsed = time.perf_counter() - start
    print(f"第 {i+1} 次: sleep(1) 实际耗时 {elapsed:.6f} 秒")
第 1 次: sleep(1) 实际耗时 1.000111 秒
第 2 次: sleep(1) 实际耗时 1.000143 秒
第 3 次: sleep(1) 实际耗时 1.000474 秒
第 4 次: sleep(1) 实际耗时 1.000104 秒
第 5 次: sleep(1) 实际耗时 1.000100 秒

每次都比 1 秒多了 0.0001 到 0.0005 秒。多数场景下这点误差无所谓,但如果在做精确计时或高频轮询(比如每 10 毫秒一次),这个偏差会累积。原因是操作系统的调度有开销,time.sleep() 只能保证"至少等这么久",不能保证"恰好等这么久"。

实战,定时检查网站状态

一个常见的用法是定时轮询。下面这个函数每 60 秒检查一次网站是否在线(为了演示,间隔改为 5 秒,只检查 3 次)。

import time
import urllib.request
import urllib.error

def uptime_bot(url, interval=5, checks=3):
    for _ in range(checks):
        try:
            response = urllib.request.urlopen(url, timeout=10)
            print(f"[{response.status}] {url} 正常")
        except urllib.error.HTTPError as e:
            print(f"[HTTP {e.code}] {url} 返回错误")
        except urllib.error.URLError as e:
            print(f"[URL错误] {url}: {e.reason}")
        time.sleep(interval)

uptime_bot("https://httpbin.org/status/404")
[HTTP 404] https://httpbin.org/status/404 返回错误
[HTTP 404] https://httpbin.org/status/404 返回错误
[HTTP 404] https://httpbin.org/status/404 返回错误

这里用 httpbin 的 404 端点做演示,实际使用时换成你要监控的 URL,间隔改回 60 秒就行。"循环 + sleep"在脚本里没毛病,脚本只有一个线程,不需要响应外部事件。

重试间隔,用装饰器把 time.sleep() 包装起来

有些操作偶尔失败(网络超时、接口没就绪),重试几次就好了,但重试之间得等一等。把"重试 + 等待"的逻辑抽成装饰器,任何函数加一行就能自动重试。

import time

def retry(delay=3, max_retries=3):
    def decorator(func):
        def wrapper(*args, **kwargs):
            last_exc = None
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exc = e
                    print(f"第 {attempt + 1} 次失败,{delay} 秒后重试: {e}")
                    time.sleep(delay)
            raise last_exc
        return wrapper
    return decorator

@retry(delay=2)
def download_file(url):
    raise ConnectionError(f"无法连接 {url}")

try:
    download_file("https://example.com/file.zip")
except ConnectionError as e:
    print(f"最终失败: {e}")
第 1 次失败,2 秒后重试: 无法连接 https://example.com/file.zip
第 2 次失败,2 秒后重试: 无法连接 https://example.com/file.zip
第 3 次失败,2 秒后重试: 无法连接 https://example.com/file.zip
最终失败: 无法连接 https://example.com/file.zip

time.sleep() 用在这里挺合适的,重试逻辑本身就是同步的,不需要响应外部事件。装饰器的好处,就是把重试逻辑从业务代码里抽出来,省得每次都手写 for 循环加 try/except。

以上场景 time.sleep() 都没问题。下面三个场景,它就开始坑人了。

多线程,time.sleep() 让线程退不掉,改用 Event.wait()

你有个后台线程在做数据迁移,每批操作之间要等 5 秒,免得压垮数据库。time.sleep(5) 能实现等待,但麻烦来了,你想停掉这个线程的时候(比如用户按了 Ctrl+C),线程得等 sleep 结束才能响应退出信号。sleep 剩 4 秒,你就得干等 4 秒。

举个更具体的例子。你的程序有个后台线程,每隔 10 秒往日志里写一条心跳记录。你用的是 time.sleep(10):

import threading
import time

def heartbeat():
    for i in range(2):
        print(f"心跳 {i} [{time.strftime('%H:%M:%S')}]")
        time.sleep(5)

t = threading.Thread(target=heartbeat)
t.start()
time.sleep(2)
print(f"主程序要退出了... [{time.strftime('%H:%M:%S')}]")

心跳 0 [16:53:45]
主程序要退出了... [16:53:47]
心跳 1 [16:53:50]
(程序总共跑了 10 秒才退出,而不是主线程希望的 2 秒)

主程序想退出,线程正卡在 sleep 中间,Python 要等非 daemon 线程跑完才退出。上面这个例子,主线程 2 秒就想走了,但实际等了 10 秒。这在生产环境里很烦人,特别是 sleep 时间长的时候(比如 60 秒的监控间隔)。

threading.Event 的 wait() 方法就是干这个的,也能等待指定时间,但可以立刻被中断。

import threading
import logging
import time

logging.basicConfig(level=logging.DEBUG, format="%(threadName)s: %(message)s")

def worker(interval, stop_event):
    while not stop_event.is_set():
        logging.debug("工作中...")
        stop_event.wait(timeout=interval)
    logging.debug("退出")

stop_event = threading.Event()
t1 = threading.Thread(target=worker, args=(2, stop_event), name="Worker-1")
t2 = threading.Thread(target=worker, args=(3, stop_event), name="Worker-2")
t1.start()
t2.start()

try:
    while not stop_event.wait(timeout=5):
        logging.debug("主线程仍在运行")
except KeyboardInterrupt:
    logging.debug("收到中断信号")
    stop_event.set()

t1.join()
t2.join()
logging.debug("所有线程已退出")
Worker-1: 工作中...
Worker-2: 工作中...
Worker-1: 工作中...
Worker-2: 工作中...
Worker-1: 工作中...
MainThread: 主线程仍在运行
MainThread: 收到中断信号
Worker-1: 退出
Worker-2: 退出
MainThread: 所有线程已退出

Event.wait(timeout=N):N 秒内 event 被 set() 了,它立刻返回 True,线程马上退出循环。超时了就返回 False,线程继续下一轮。比 time.sleep() 优雅得多,不用等 sleep 跑完才能停。

异步代码,time.sleep() 冻结整个事件循环,改用 asyncio.sleep()

异步编程的思路:一个线程同时处理多个任务,哪个任务在等 IO 就先挂起它,去跑其他任务。time.sleep() 会把整个线程(也就是整个事件循环)冻住,所有任务都卡住,异步的优势全没了。

asyncio.sleep() 只挂起当前协程,不阻塞事件循环,其他协程可以继续跑。下面这个例子用时间戳直观展示两者的区别。

import asyncio
import time

async def task(name, delay):
    print(f"{time.strftime('%H:%M:%S')} {name} 开始")
    await asyncio.sleep(delay)
    print(f"{time.strftime('%H:%M:%S')} {name} 结束(等了 {delay} 秒)")

async def main():
            await asyncio.gather(task("A", 2), task("B", 1))

asyncio.run(main())
14:44:23 A 开始
14:44:23 B 开始
14:44:24 B 结束(等了 1 秒)
14:44:25 A 结束(等了 2 秒)

B 在第 1 秒就结束了,不用等 A。两个任务是并发执行的,总共只花了 2 秒而不是串行的 3 秒。

如果换成 time.sleep(),效果是这样的:

import time

print("=== 串行 ===")
start = time.perf_counter()
for name, delay in [("A", 2), ("B", 1)]:
    print(f"{time.strftime('%H:%M:%S')} {name} 开始")
    time.sleep(delay)
    print(f"{time.strftime('%H:%M:%S')} {name} 结束(等了 {delay} 秒)")
print(f"串行总耗时: {time.perf_counter() - start:.1f} 秒")
=== 串行 ===
16:40:28 A 开始
16:40:30 A 结束(等了 2 秒)
16:40:30 B 开始
16:40:31 B 结束(等了 1 秒)
串行总耗时: 3.0 秒

对比一下,串行版本里 B 必须等 A 跑完才开始,总共 3 秒。而用 asyncio.sleep() 的并发版本,两个任务同时开始,B 先结束,总共只要 2 秒。差异来自 time.sleep() 把整个线程焊死了,事件循环没法切换到其他协程。

说个实际的场景,调 API 有速率限制,需要在请求之间加间隔。用 asyncio.sleep() 可以在不阻塞事件循环的前提下控制请求频率,同时其他协程(比如处理用户输入)不受影响。

GUI 程序,time.sleep() 冻死界面,改用 .after()

GUI 框架(Tkinter、wxPython)在主线程跑一个事件循环,处理用户点击、窗口重绘这些事。在事件循环里调用 time.sleep(),会导致界面直接冻住,按钮点不动,窗口拖不了,Windows 上甚至会弹出"程序未响应"。

先看错误示范。

import tkinter as tk
import time

root = tk.Tk()
root.title("冻结演示")
root.geometry("300x150")

def on_click():
    print("按钮被点击")
    time.sleep(3)
    print("3 秒到了")

btn = tk.Button(root, text="点我(会冻结 3 秒)", command=on_click)
btn.pack(pady=50)
root.mainloop()

点击按钮后,整个窗口冻住 3 秒,什么都做不了。

正确做法是用 Tkinter 的 .after() 方法。它接受两个参数,等待的毫秒数和回调函数。调用后立刻返回,不阻塞事件循环。

import tkinter as tk

root = tk.Tk()
root.title("非阻塞演示")
root.geometry("300x150")

def delayed_action():
    print("3 秒到了")

def on_click():
    root.after(3000, delayed_action)
    print("已安排 3 秒后执行,界面不会卡")

btn = tk.Button(root, text="点我(不会冻结)", command=on_click)
btn.pack(pady=50)
root.mainloop()
已安排 3 秒后执行,界面不会卡
(3 秒后)
3 秒到了
(此输出为描述,未经实际验证——验证环境缺少 python3-tk)

用 .after() 之后,按钮可以反复点击,每次点击都安排一个延迟回调,界面始终流畅。wxPython 的等价方法是 wx.CallLater(),思路一样,安排延迟回调而不是阻塞线程。

决策速查表

场景 用什么 为什么
普通脚本(包括重试间隔) time.sleep() 只有一个线程,不怕阻塞
多线程,工作间隔 Event.wait() 可以被 set() 立即中断,不用等 sleep 跑完
异步代码,协程暂停 asyncio.sleep() 只挂起当前协程,不冻结事件循环
GUI,延迟操作 .after() / wx.CallLater() 不阻塞 GUI 事件循环,界面保持响应

说到底就一句话,time.sleep() 阻塞的是整个线程。只要你的程序在一个线程里跑多个任务(多个协程共享一个线程、GUI 事件循环在主线程处理用户操作),就不能用它,得换成不阻塞或可中断的方案。