多线程基础
运维脚本常需同时检查多台主机、多个 URL 或多个 API。Python threading 模块适合 I/O 密集型任务(网络请求、SSH、读文件),可在等待响应时切换线程,提高吞吐。CPU 密集计算仍应使用多进程,本文不展开。
核心概念
| 概念 | 说明 |
|---|---|
| Thread | 操作系统调度的轻量执行单元 |
| GIL | 全局解释器锁,同一时刻仅一个线程执行 Python 字节码 |
| I/O 释放 GIL | 网络/磁盘等待时 GIL 会释放,多线程仍有效 |
创建与启动线程
#!/usr/bin/env python3
"""为每台主机启动独立线程执行 ping 检查(示例用 sleep 模拟 I/O)。"""
import socket
import threading
import time
from typing import Callable
def check_host(ip: str, results: list, lock: threading.Lock) -> None:
"""模拟远程检查:解析主机名 + 等待响应。"""
try:
hostname = socket.gethostbyaddr(ip)[0]
time.sleep(0.5) # 模拟 SSH/HTTP 延迟
status = "ok"
except OSError:
hostname = "unknown"
status = "fail"
# 多线程写共享列表必须加锁
with lock:
results.append({"ip": ip, "hostname": hostname, "status": status})
def run_checks(hosts: list[str]) -> list[dict]:
results: list[dict] = []
lock = threading.Lock()
threads: list[threading.Thread] = []
for ip in hosts:
t = threading.Thread(target=check_host, args=(ip, results, lock), name=f"check-{ip}")
threads.append(t)
t.start()
# 等待全部线程结束
for t in threads:
t.join()
return results
def main() -> None:
hosts = ["10.0.0.1", "10.0.0.2", "10.0.0.3"]
report = run_checks(hosts)
for row in report:
print(row)
if __name__ == "__main__":
main()
Lock 与线程安全
counter = 0
lock = threading.Lock()
def increment() -> None:
global counter
for _ in range(100_000):
with lock: # 保证 read-modify-write 原子性
counter += 1
不加锁时 counter += 1 可能丢失更新。共享状态(列表、字典、计数器)写入时务必同步。
Event 与超时控制
def worker(stop_event: threading.Event) -> None:
while not stop_event.is_set():
# 周期性巡检逻辑
time.sleep(1)
print("收到停止信号,退出")
def run_with_timeout(seconds: float) -> None:
stop = threading.Event()
t = threading.Thread(target=worker, args=(stop,))
t.start()
time.sleep(seconds)
stop.set() # 通知线程结束
t.join(timeout=5)
Event 适合优雅停止后台巡检线程;join(timeout=...) 防止主线程无限等待。
何时用原生线程
手动 Thread 适合:
- 少量固定后台任务(日志采集、心跳)
- 学习并发模型
批量同类任务(上百台主机 HTTP 探活)更推荐下一章的 ThreadPoolExecutor,可限制并发数、复用线程、统一收集异常。
提示
- 默认线程为 daemon=False,主程序会等线程结束;守护线程随主进程退出
- 调试时给线程
name=便于日志定位 - 线程数不是越多越好,过多会增加上下文切换与目标端压力
小结
- I/O 密集型运维任务可用
threading提速 - 共享数据写操作必须加
Lock - 批量任务优先用线程池(下一章)
下一章:ThreadPoolExecutor 实现批量主机巡检。