一、库的简介:并发执行的多面手

在日常开发中,你会遇到这样的场景:一个程序需要一边下载文件,一边响应用户的点击操作;或者一个监控工具需要同时监听多个设备的状态,而不是挨个等待。如果所有任务都按顺序执行,那么耗时的操作(如网络请求、文件读写)就会阻塞整个程序,用户界面卡死,体验极差。Python 的 threading 标准库提供了多线程编程的支持,让你可以在同一个进程内创建多个线程,并发地执行不同的任务。在实际生活中,threading 可以驱动一个 GUI 程序的后台计算(比如文本编辑器自动保存文档时不卡住输入),可以为爬虫程序同时请求多个网页,也可以实现一个定时器在特定时间执行回调。需要注意的是,由于 Python 全局解释器锁(GIL)的存在,threading 对于 CPU 密集型任务(如大量数值计算)并不能提升性能,但对于 I/O 密集型任务(网络请求、磁盘读写、数据库查询),它可以极大地提高程序的吞吐量和响应速度。

二、安装 threading

threading 是 Python 的内置标准库,无需任何 pip install 命令,导入即可使用:

python

import threading

三、基本用法

以下代码假设已经导入 import threading

第一步:创建并启动一个线程

使用 threading.Thread(target=函数名, args=(参数,)) 创建线程,调用 start() 启动。

python

import threading
import time

def print_numbers():
    for i in range(5):
        time.sleep(0.5)
        print(f"子线程: {i}")

t = threading.Thread(target=print_numbers)
t.start()          # 启动子线程
print("主线程继续执行...")
t.join()           # 等待子线程结束
print("程序结束")

第二步:传递参数给线程函数

通过 args 传递位置参数,kwargs 传递关键字参数。

python

def greet(name, delay=1):
    time.sleep(delay)
    print(f"Hello, {name}")

t1 = threading.Thread(target=greet, args=("Alice",), kwargs={"delay": 2})
t1.start()

第三步:守护线程(Daemon Thread)

将线程设置为守护线程(daemon=True),主线程结束时,守护线程会立即被强制终止,不等待其完成。常用于后台任务,如心跳检测、日志刷新。

python

def background_task():
    while True:
        time.sleep(1)
        print("后台运行...")

t = threading.Thread(target=background_task, daemon=True)
t.start()
time.sleep(3)
print("主线程退出,守护线程随之结束")

第四步:获取当前线程信息

threading.current_thread() 可以获取当前线程对象,常用于调试。

python

def worker():
    print(f"线程 {threading.current_thread().name} 开始工作")

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

四、高级用法

1. 线程同步 —— Lock

多个线程同时修改共享变量会导致数据竞争。使用 threading.Lock 保证同一时刻只有一个线程执行临界区代码。

python

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100000):
        with lock:      # 上下文管理器自动获取和释放锁
            counter += 1

threads = [threading.Thread(target=increment) for _ in range(5)]
for t in threads:
    t.start()
for t in threads:
    t.join()
print(counter)  # 应为 500000,不加锁会远小于此值

2. RLock —— 可重入锁

同一个线程可以多次获取 RLock,避免死锁。

python

rlock = threading.RLock()
def recursive_func(n):
    with rlock:
        if n > 0:
            recursive_func(n-1)

3. Event —— 线程间信号通信

一个线程等待 Event 被设置,另一个线程设置它,用于协调启动或停止任务。

python

event = threading.Event()

def waiter():
    print("等待事件...")
    event.wait()
    print("事件已触发,继续执行")

def setter():
    time.sleep(2)
    print("设置事件")
    event.set()

threading.Thread(target=waiter).start()
threading.Thread(target=setter).start()

五、实际应用场景案例

场景一:多线程下载网页(I/O 并发)

假设你需要从多个 URL 下载内容,使用线程池可以显著减少总耗时。这里展示一个简单的线程池实现:

python

import threading
import requests   # 需要安装 pip install requests
import time

urls = [
    "https://www.example.com",
    "https://www.python.org",
    "https://www.github.com",
] * 5   # 共15个任务

results = {}
lock = threading.Lock()

def download(url):
    try:
        resp = requests.get(url, timeout=5)
        with lock:
            results[url] = len(resp.text)
        print(f"完成: {url} 大小 {len(resp.text)}")
    except Exception as e:
        print(f"失败: {url} - {e}")

start = time.time()
threads = []
for url in urls:
    t = threading.Thread(target=download, args=(url,))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

print(f"总共下载 {len(results)} 个页面,耗时 {time.time()-start:.2f} 秒")

场景二:生产者-消费者模式(使用 Queue

queue.Queue 是线程安全的队列,适合在线程之间传递数据。下面的例子中,生产者不断生成任务,多个消费者并发处理。

python

import threading
import queue
import time
import random

q = queue.Queue(maxsize=10)

def producer():
    for i in range(20):
        item = f"任务-{i}"
        q.put(item)
        print(f"生产: {item}")
        time.sleep(random.uniform(0.1, 0.3))
    q.put(None)   # 结束信号

def consumer(name):
    while True:
        item = q.get()
        if item is None:
            q.put(None)   # 传递给其他消费者
            break
        print(f"  消费者 {name} 处理: {item}")
        time.sleep(random.uniform(0.2, 0.5))
        q.task_done()

prod_thread = threading.Thread(target=producer)
cons_threads = [threading.Thread(target=consumer, args=(f"C{i}",)) for i in range(3)]

prod_thread.start()
for t in cons_threads:
    t.start()
prod_thread.join()
for t in cons_threads:
    t.join()
print("所有任务完成")

场景三:定时器(Timer)—— 延时执行任务

threading.Timer 可以在指定时间后执行一个函数,非常适合做延迟提醒、定时重试等。

python

def remind(message):
    print(f"提醒: {message}")

print("设置5秒后提醒")
timer = threading.Timer(5.0, remind, args=("该休息一下了",))
timer.start()

# 也可以取消定时器
# timer.cancel()

六、结尾互动

threading 是 Python 并发编程的基石之一。它为 I/O 密集型任务提供了简单优雅的并发模型,让程序能够充分利用等待时间去做其他事情。但要注意,多线程并非万能药——GIL 限制了 CPU 密集型任务的并行能力,此时应考虑 multiprocessing 或异步编程。同时,线程间的数据竞争和死锁也需要细心规避,合理使用锁、队列等同步工具是写出健壮多线程程序的关键。

你是否有过用 threading 加速爬虫或后台任务的经验?或者曾踩过死锁、变量竞争的大坑?欢迎在评论区分享你的故事和心得。不妨试试用 threading 写一个“同时下载多张图片并压缩”的小工具,感受并发带来的效率提升!

更多推荐