触发SIGINT执行shutdown handler时socket recv阻塞致线程无法正常退出
核心问题根因
- 你在
shutdown_handler里关闭的是监听socketself.sock,但接收线程实际是阻塞在已建立连接的self.conn的recv()调用上,关闭监听socket完全不会打断self.conn上的阻塞接收 - 接收线程的
recv()设置了无限阻塞(代码里手动把对应socket超时设为None),没有超时机制的情况下,只要没有对端数据/对端断开信号,线程就会一直卡在recv调用里,导致后续的t.join()永远阻塞,给人一种代码没执行到join步骤的错觉 - 原代码
connect方法中连续写了两个except Exception,第二个超时捕获分支永远不会被触发,属于无效逻辑
修正方案
替换原有Example类实现,保证shutdown流程可正常终止接收线程、释放所有socket资源:
import socket import threading class Example(object): def __init__(self): # 增加线程运行标志位,用于优雅终止接收线程 self._recv_thread_running = True self.conn = None self.sock = None self.t = None def connect(self, host='', port=2000): try: self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置地址复用,避免程序重启时端口被占用报错 self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.sock.settimeout(10) self.sock.bind((host, port)) self.sock.listen(1) # accept受10秒超时控制,超时会抛独立的socket.timeout异常 self.conn, self.addr = self.sock.accept() # 给已建立的连接设置短超时,避免recv永久阻塞 self.conn.settimeout(1) # 重置运行标志 self._recv_thread_running = True self.t = threading.Thread(target=self.recv_msg) # 设为守护线程,避免主程序退出时线程残留 self.t.daemon = True self.t.start() return True # 按异常粒度分别捕获,替换原来重复的Exception捕获逻辑 except socket.timeout: print("连接建立超时") self._cleanup_resource() return False except Exception as e: print(f"连接建立失败: {e}") self._cleanup_resource() return False def recv_msg(self): while self._recv_thread_running: try: # 因为设置了1秒超时,这里不会永久阻塞,每1秒会检查一次运行标志 recv_msg = self.conn.recv(1024) if not recv_msg: # 对端断开连接时recv会返回空字节,直接退出循环 break print(recv_msg) except socket.timeout: # 超时是预期内的,跳过不处理,继续循环检查运行标志 continue # 线程退出前清理连接资源 if self.conn: self.conn.close() def shutdown_handler(self): # 先修改运行标志,通知接收线程退出 self._recv_thread_running = False try: # 主动关闭已建立的连接socket,打断可能正在阻塞的recv if self.conn: self.conn.shutdown(socket.SHUT_RDWR) # 等待接收线程退出,设置超时避免永久卡住 if self.t: self.t.join(timeout=2) # 最后关闭监听socket if self.sock: self.sock.close() except Exception as e: print(f"关闭流程异常: {e}") finally: self._cleanup_resource() def _cleanup_resource(self): # 统一做资源兜底清理,避免资源泄漏 self._recv_thread_running = False for resource in [self.conn, self.sock]: if resource: try: resource.close() except Exception: pass
主程序部分的原有逻辑不需要改动,直接配合修正后的类即可正常响应Ctrl+C信号触发的shutdown流程。
关键改动说明
- 用线程运行标志位替代原来的
while True无限循环,通过标志位控制线程退出流程,避免强杀线程带来的资源泄漏 - 给
conn连接设置1秒的接收超时,让原本永久阻塞的recv()变成周期性唤醒,既能及时接收网络数据,又能定期检查退出标志 - shutdown流程中优先关闭已建立的连接
self.conn,而不是只关监听socket,从根源上打断阻塞的recv调用 - 所有socket资源在关闭前都做存在性判断,避免空对象调用close报错
- 给线程join设置2秒的超时兜底,哪怕线程因为异常没来得及退出,主流程也不会永久卡住
内容的提问来源于stack exchange,提问作者rluq
相关产品推荐
相关产品推荐

