python 线程机制

在 Python 中,线程是实现并发编程的一种方式,允许程序同时执行多个任务。以下是 Python 线程机制的详细解说:

1. 线程基础概念

线程 vs 进程

  • 进程:程序在操作系统中的一次执行实例,拥有独立的内存空间和系统资源。
  • 线程:进程内的一个执行单元,共享进程的内存和资源。
  • 区别:线程轻量级,创建和切换开销小;进程隔离性强,稳定性高。

Python 线程的特点

  • 全局解释器锁(GIL):CPython 解释器同一时间只能执行一个线程的字节码,导致多线程无法利用多核 CPU 的并行计算能力。
  • 适用场景:IO 密集型任务(如网络请求、文件读写),不适合 CPU 密集型任务(如科学计算)。

2. 线程模块:threading

Python 标准库的 threading 模块提供了高级线程 API,支持创建、控制和管理线程。

创建线程的两种方式

方式 1:继承 Thread
import threading

class MyThread(threading.Thread):
    def __init__(self, name):
        super().__init__()
        self.name = name
    
    def run(self):  # 线程执行的入口函数
        print(f"线程 {self.name} 开始")
        for i in range(3):
            print(f"{self.name}: {i}")
        print(f"线程 {self.name} 结束")

# 创建并启动线程
t1 = MyThread("线程A")
t2 = MyThread("线程B")
t1.start()  # 调用 start() 方法启动线程,会自动调用 run()
t2.start()
t1.join()   # 等待线程结束
t2.join()
方式 2:传入目标函数
import threading

def worker(name):
    print(f"线程 {name} 开始")
    for i in range(3):
        print(f"{name}: {i}")
    print(f"线程 {name} 结束")

# 创建并启动线程
t1 = threading.Thread(target=worker, args=("线程C",))
t2 = threading.Thread(target=worker, args=("线程D",))
t1.start()
t2.start()
t1.join()
t2.join()

线程同步机制

锁(Lock)

用于保护共享资源,避免多线程竞争导致的数据不一致。

import threading

counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100000):
        lock.acquire()  # 获取锁
        try:
            counter += 1
        finally:
            lock.release()  # 释放锁(确保异常时也能释放)

# 等价写法(使用 with 语句自动管理锁)
def increment_with():
    global counter
    for _ in range(100000):
        with lock:  # 自动获取和释放锁
            counter += 1

t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"Counter: {counter}")  # 正确结果应为 200000
RLock(可重入锁)

允许同一线程多次获取同一把锁,避免死锁。

lock = threading.RLock()

def recursive_function(n):
    with lock:
        if n == 0:
            return
        print(f"递归层级: {n}")
        recursive_function(n-1)

threading.Thread(target=recursive_function, args=(5,)).start()
信号量(Semaphore)

控制同时访问资源的线程数量。

semaphore = threading.Semaphore(2)  # 最多允许2个线程同时访问

def worker(id):
    with semaphore:
        print(f"线程 {id} 获得信号量")
        # 模拟耗时操作
        import time
        time.sleep(1)
        print(f"线程 {id} 释放信号量")

for i in range(5):
    threading.Thread(target=worker, args=(i,)).start()
条件变量(Condition)

用于线程间的消息传递和协作。

condition = threading.Condition()
data_ready = False

def producer():
    global data_ready
    with condition:
        print("生产者: 准备数据...")
        import time
        time.sleep(2)
        data_ready = True
        condition.notify()  # 通知等待的线程

def consumer():
    with condition:
        condition.wait()  # 等待通知
        print("消费者: 处理数据")

threading.Thread(target=producer).start()
threading.Thread(target=consumer).start()

守护线程(Daemon Thread)

守护线程是后台线程,当主线程退出时会自动终止。

import threading
import time

def daemon_worker():
    while True:
        print("守护线程运行中...")
        time.sleep(1)

# 创建守护线程
t = threading.Thread(target=daemon_worker)
t.daemon = True  # 设置为守护线程
t.start()

# 主线程继续执行,3秒后退出
time.sleep(3)
print("主线程退出,守护线程自动终止")

线程间通信

使用队列(queue.Queue)实现线程安全的消息传递。

from queue import Queue
import threading

def producer(queue):
    for i in range(5):
        queue.put(f"数据 {i}")
        print(f"生产者: 放入数据 {i}")

def consumer(queue):
    while True:
        data = queue.get()
        if data is None:  # 终止信号
            break
        print(f"消费者: 取出 {data}")
        queue.task_done()  # 通知队列任务已完成

q = Queue()
t1 = threading.Thread(target=producer, args=(q,))
t2 = threading.Thread(target=consumer, args=(q,))
t1.start()
t2.start()
t1.join()
q.put(None)  # 发送终止信号
t2.join()

3. 线程池(Python 3.2+)

使用 concurrent.futures.ThreadPoolExecutor 管理线程池,避免手动创建和销毁线程的开销。

from concurrent.futures import ThreadPoolExecutor

def task(n):
    print(f"处理任务 {n}")
    import time
    time.sleep(1)
    return n * n

# 创建线程池,最多3个工作线程
with ThreadPoolExecutor(max_workers=3) as executor:
    # 提交任务并获取结果
    results = list(executor.map(task, range(5)))

print("结果:", results)  # 输出: [0, 1, 4, 9, 16]

4. GIL 对 Python 线程的影响

  • GIL(全局解释器锁):CPython 解释器的限制,同一时间只能有一个线程执行 Python 字节码。
  • 影响
    • IO 密集型任务:线程在等待 IO 时会释放 GIL,其他线程可执行,因此多线程有效。
    • CPU 密集型任务:线程持续占用 GIL,无法利用多核,性能甚至低于单线程。

验证 GIL 的影响

import threading
import time

# CPU 密集型任务
def cpu_task():
    x = 0
    for i in range(10**7):
        x += i
    return x

# 单线程
start = time.time()
cpu_task()
cpu_task()
print(f"单线程耗时: {time.time() - start:.2f}s")

# 多线程
start = time.time()
t1 = threading.Thread(target=cpu_task)
t2 = threading.Thread(target=cpu_task)
t1.start()
t2.start()
t1.join()
t2.join()
print(f"多线程耗时: {time.time() - start:.2f}s")  # 通常比单线程更慢

5. 替代方案:多进程与异步编程

多进程(multiprocessing

每个进程有独立的 Python 解释器和 GIL,适合 CPU 密集型任务。

from multiprocessing import Process

def cpu_intensive_task():
    # CPU 密集型计算
    sum([i*i for i in range(10**7)])

if __name__ == "__main__":
    p1 = Process(target=cpu_intensive_task)
    p2 = Process(target=cpu_intensive_task)
    p1.start()
    p2.start()
    p1.join()
    p2.join()

异步编程(asyncio

使用协程(coroutine)实现单线程内的并发,适合 IO 密集型任务。

import asyncio

async def fetch_data(url):
    print(f"开始请求 {url}")
    await asyncio.sleep(1)  # 模拟 IO 等待
    print(f"完成请求 {url}")
    return f"data from {url}"

async def main():
    tasks = [fetch_data(f"url{i}") for i in range(3)]
    results = await asyncio.gather(*tasks)
    print(results)

asyncio.run(main())

6. 线程安全注意事项

  1. 避免共享状态:尽量减少多线程共享的全局变量。
  2. 使用线程安全的数据结构:如 queue.Queue,避免直接操作列表、字典等。
  3. 正确同步:使用锁、信号量等机制保护共享资源。
  4. 避免死锁:按固定顺序获取锁,使用 with 语句确保锁释放。

总结

  • 适用场景:Python 线程适合 IO 密集型任务,如网络爬虫、文件处理。
  • 同步工具LockRLockSemaphoreCondition 用于线程协作。
  • 替代方案:CPU 密集型任务使用多进程,高并发 IO 使用异步编程。

合理使用线程机制可以提高程序的并发能力,但需注意 GIL 的限制和线程安全问题。

posted @ 2025-05-10 16:38  天堂面包  阅读(28)  评论(0)    收藏  举报