Python requests.get带stream=True偶发挂起、Ctrl+C中断无效问题排查
挂起问题的核心诱因
根据提供的完整调用栈可明确,程序阻塞位置并非图片内容下载阶段,而是卡在底层C层面的TLS/SSL握手阻塞调用(栈尾的self._sslobj.do_handshake()),具体诱发因素可拆解为三点:
- 代码未配置请求超时参数。当前的
requests.get(url, stream=True)调用未传入任何timeout配置,requests库默认采用无限等待策略:只要TCP通路建立后,目标服务不返回TLS握手报文、或者中间网络设备(防火墙、代理、NAT网关)直接丢弃数据包不做响应,程序就会永久卡在系统调用层等待,不会主动触发超时退出。 - Ctrl+C无法即时中断的底层原因。Python的
KeyboardInterrupt信号响应依赖解释器在字节码执行间隙检查信号标记,但当代码进入C扩展层的阻塞系统调用(比如SSL握手、socket读操作)时,这个调用会一直等到内核返回结果才会切回Python的执行上下文。此时即便按下Ctrl+C,对应的中断标记也要等阻塞调用自行结束(Windows系统默认的TCP连接超时往往长达数分钟)才会被处理,这就是需要等待极久才能强制终止的根本原因。 - 偶发触发的场景条件。这类挂起大多出现在遇到半开连接、目标服务握手阶段无响应、代理链路丢包的情况:此时网络路径上既不返回TCP连接重置报文,也不回传握手响应,没有超时兜底的逻辑就会直接卡死流程。
对应修复方案
针对上述问题,仅需要修改代码配置即可彻底解决:
- 所有
requests请求必须显式配置超时参数,避免无限等待。超时值可以拆分为独立的两部分适配下载场景:- 连接超时:从发起请求到完成TCP+TLS握手建链的最长等待时间,通常设置为5-10秒即可覆盖绝大多数正常网络场景
- 读取超时:建链成功后,两次接收数据块之间的最长等待时间,适配大文件流式下载的需求
修改后的请求写法参考:
配置超时后,即便程序卡在TLS握手阶段,也会在设定的超时阈值到达后主动抛出response = requests.get( url, stream=True, timeout=(10, 30) # 元组依次对应连接超时、读取超时 )Timeout异常终止当前阻塞流程,从根本上把最长阻塞时间限制在可接受范围内,同时大幅提升Ctrl+C的中断响应速度。 - 可选的稳定性优化:当前代码用
for chunk in response遍历响应内容的写法没有固定块大小,可替换为response.iter_content(chunk_size=8192)的方式,在大文件下载场景下稳定性更强。
内容的提问来源于stack exchange,提问作者Tianyu Qi
相关产品推荐
相关产品推荐

