一、线程基础概念
1.1 进程与线程的关系
进程是操作系统资源分配的基本单位,它是程序的一次执行过程。当我们将程序加载到内存中运行时,系统会为它分配CPU、内存、文件句柄等资源,这时就形成了一个进程。
线程是CPU调度的基本单位,它是进程中的一个执行流。一个进程可以包含多个线程,这些线程共享进程的资源,但每个线程有自己的执行路径和栈空间。
关键区别:
-
进程间相互独立,线程间共享进程资源
-
进程切换开销大,线程切换开销小
-
进程通信需要IPC机制,线程可直接读写进程数据段
1.2 为什么需要线程
虽然进程已经实现了多道编程,但仍存在两个主要缺陷:
-
单任务限制:传统进程一次只能执行一个任务
-
阻塞问题:进程某部分阻塞会导致整个进程挂起
现实类比:将上课看作一个进程,我们需要同时:
-
耳朵听老师讲课(线程1)
-
手上记笔记(线程2)
-
脑子思考问题(线程3)
如果没有线程机制,这三件事只能顺序执行,效率低下。
二、Python线程实现
2.1 线程模块选择
Python提供了多个线程相关模块:
-
thread
:基础线程模块(已过时,不推荐) -
threading
:高级线程接口(推荐使用) -
Queue
:线程安全队列实现
2.2 创建线程的两种方式
方式一:使用Thread类直接创建
from threading import Thread
import time
def task(num):
time.sleep(0.5)
print(f"线程{num}执行")
if __name__ == '__main__':
threads = []
for i in range(3):
t = Thread(target=task, args=(i,))
threads.append(t)
t.start()
for t in threads:
t.join() # 等待所有线程完成
方式二:继承Thread类创建
from threading import Thread
class MyThread(Thread):
def __init__(self, num):
super().__init__()
self.num = num
def run(self):
print(f"自定义线程{self.num}执行")
if __name__ == '__main__':
t = MyThread(1)
t.start()
t.join()
2.3 Thread类常用方法
方法名 | 描述 |
---|---|
start() | 启动线程 |
join([timeout]) | 等待线程终止 |
is_alive() | 返回线程是否存活 |
name | 线程名称 |
ident | 线程标识符 |
daemon | 是否为守护线程 |
2.4 threading模块常用函数
函数 | 描述 |
---|---|
threading.current_thread() | 返回当前线程对象 |
threading.active_count() | 当前活跃线程数 |
threading.enumerate() | 返回所有活跃线程列表 |
threading.main_thread() | 返回主线程对象 |
三、线程与进程性能对比
3.1 创建开销对比
from threading import Thread
from multiprocessing import Process
import time
def task(num):
pass
# 进程测试
start = time.time()
processes = []
for i in range(100):
p = Process(target=task, args=(i,))
p.start()
processes.append(p)
[p.join() for p in processes]
print("多进程耗时:", time.time()-start) # 约11秒
# 线程测试
start = time.time()
threads = []
for i in range(100):
t = Thread(target=task, args=(i,))
t.start()
threads.append(t)
[t.join() for t in threads]
print("多线程耗时:", time.time()-start) # 约0.02秒
3.2 PID对比
import os
from threading import Thread
from multiprocessing import Process
def show_pid(label):
print(f"{label} PID:", os.getpid())
# 进程
p = Process(target=show_pid, args=("进程",))
p.start() # 显示不同PID
# 线程
t = Thread(target=show_pid, args=("线程",))
t.start() # 显示相同PID
四、守护线程详解
4.1 守护线程 vs 守护进程
守护进程特点:
-
随主进程结束而立即结束
-
主进程会等待非守护子进程完成
-
守护进程主要用于服务主进程
守护线程特点:
-
随主线程结束而结束(实际是进程内所有非守护线程结束后)
-
主线程会等待所有非守护线程完成
-
守护线程通常用于后台支持任务
4.2 代码示例
from threading import Thread
import time
def daemon_task():
print("守护线程开始")
time.sleep(5)
print("守护线程结束") # 可能不会执行
def normal_task():
print("普通线程开始")
time.sleep(2)
print("普通线程结束")
if __name__ == '__main__':
d = Thread(target=daemon_task)
d.daemon = True
n = Thread(target=normal_task)
d.start()
n.start()
print("主线程结束")
# 程序会在普通线程结束后退出,守护线程可能被强制结束
五、线程同步机制
5.1 互斥锁(Lock)
from threading import Thread, Lock
counter = 0
lock = Lock()
def increment():
global counter
for _ in range(100000):
lock.acquire()
counter += 1
lock.release()
threads = []
for i in range(5):
t = Thread(target=increment)
threads.append(t)
t.start()
for t in threads:
t.join()
print("Final counter:", counter) # 正确结果500000
5.2 死锁问题与解决方案
死锁示例:
from threading import Thread, Lock
lock1 = Lock()
lock2 = Lock()
def func1():
lock1.acquire()
print("Func1获取lock1")
lock2.acquire()
print("Func1获取lock2")
lock2.release()
lock1.release()
def func2():
lock2.acquire()
print("Func2获取lock2")
lock1.acquire()
print("Func2获取lock1")
lock1.release()
lock2.release()
t1 = Thread(target=func1)
t2 = Thread(target=func2)
t1.start()
t2.start()
t1.join()
t2.join()
解决方案:使用RLock(可重入锁)
from threading import RLock
lock = RLock()
def recursive_func(n):
if n > 0:
lock.acquire()
print(f"获取锁,n={n}")
recursive_func(n-1)
lock.release()
recursive_func(3)
5.3 信号量(Semaphore)
from threading import Semaphore, Thread
import time
sem = Semaphore(3) # 允许最多3个线程同时访问
def task(name):
print(f"{name} 等待获取信号量")
sem.acquire()
print(f"{name} 获取了信号量")
time.sleep(2)
sem.release()
print(f"{name} 释放了信号量")
for i in range(10):
t = Thread(target=task, args=(f"Thread-{i}",))
t.start()
5.4 事件(Event)
from threading import Event, Thread
import time
event = Event()
def waiter():
print("等待事件触发")
event.wait() # 阻塞直到事件被设置
print("事件已触发,继续执行")
def setter():
time.sleep(3)
print("设置事件")
event.set() # 触发事件
Thread(target=waiter).start()
Thread(target=setter).start()
5.5 条件变量(Condition)
from threading import Condition, Thread
condition = Condition()
items = []
def consumer():
condition.acquire()
if not items:
print("消费者等待...")
condition.wait() # 释放锁并等待
print(f"消费物品: {items.pop()}")
condition.release()
def producer():
condition.acquire()
items.append("新产品")
print("生产者通知...")
condition.notify() # 唤醒一个等待线程
condition.release()
Thread(target=consumer).start()
Thread(target=producer).start()
六、线程池与高级用法
6.1 使用ThreadPoolExecutor
from concurrent.futures import ThreadPoolExecutor
import time
def task(name):
print(f"任务 {name} 开始")
time.sleep(2)
return f"任务 {name} 完成"
with ThreadPoolExecutor(max_workers=3) as executor:
futures = [executor.submit(task, i) for i in range(5)]
for future in futures:
print(future.result()) # 获取任务结果
6.2 线程局部数据
from threading import Thread, local
thread_data = local()
thread_data.x = 0
def task():
thread_data.x = 1
print(f"子线程: {thread_data.x}")
t = Thread(target=task)
t.start()
t.join()
print(f"主线程: {thread_data.x}") # 仍然是0,各线程独立
七、线程编程最佳实践
-
避免全局变量:尽量使用参数传递数据
-
合理使用锁:锁的范围要尽可能小
-
防止死锁:按固定顺序获取多个锁
-
优先使用队列:Queue是线程安全的通信方式
-
考虑GIL影响:CPU密集型任务考虑多进程
-
资源清理:确保线程结束时释放资源
-
异常处理:线程内异常不会传播到主线程
八、常见问题解答
Q:Python多线程真的能提高性能吗?
A:对于I/O密集型任务,多线程能显著提高性能;对于CPU密集型任务,由于GIL的存在,多线程可能不会提高性能,此时应考虑多进程。
Q:如何选择多线程还是多进程?
A:根据任务类型选择:
-
I/O密集型:多线程
-
CPU密集型:多进程
-
混合型:多进程+多线程
Q:线程安全的数据结构有哪些?
A:Python中的Queue、deque(需加锁)、collections.defaultdict(需加锁)等,或使用threading.local实现线程局部存储。