Python多线程

一、多线程基础核心(重点)

1. 线程与进程的本质区别

  • 核心定义
    进程是操作系统"分配资源"的最小单位(如内存、文件描述符),线程是"CPU调度"的最小单位;一个进程包含多个线程,线程共享进程的资源,无进程级隔离(进程崩溃则其下所有线程终止)。
  • 关键差异对比(重点)

多线程与多进程对比

对比维度 多线程(Threading) 多进程(Multiprocessing)
资源开销 轻量,创建/销毁快 重量,创建/销毁慢
GIL影响 受CPython的GIL限制,CPU密集型无法并行 每个进程有独立解释器,绕开GIL,支持并行
内存共享 共享进程内存,易有线程安全问题 内存隔离,需通过IPC(管道、共享内存)通信
适用场景 IO密集型(如网络请求、文件读写) CPU密集型(如数据计算、图像处理)
  • 举例
    • 多线程场景:用3个线程同时下载3部电影(下载时需等待网络响应,属于IO密集,线程切换快,效率高);
    • 多进程场景:用4个进程计算1-100000的质数(计算需占用CPU,属于CPU密集,多进程可利用4核CPU同时计算,速度比单进程快近4倍)。

2. GIL(全局解释器锁)的特性(重点)

  • 定义:CPython解释器的锁机制,确保同一时刻只有1个线程执行Python字节码。
  • 优缺点
    • 优点:简化内存管理(避免多线程同时修改对象)、单线程运行稳定;
    • 缺点:多核CPU下,CPU密集型多线程无法"真正并行",线程数量过多时会因频繁切换损耗性能。
  • 举例
    用8线程处理大型Excel数据计算(CPU密集),实际只有1个线程在执行,其余线程排队等待GIL,最终效率甚至比单线程低(切换线程需要时间);若换成8进程,每个进程占用1个CPU核心,8个核心同时计算,效率会大幅提升。

二、Python多线程实现(threading模块)

1. 线程创建的3种方式(重点)

(1)基础无参线程
  • 核心逻辑 :通过threading.Thread绑定目标函数,用start()启动线程(不可直接调用函数,否则会在主线程同步执行)。
  • 例如:创建1个线程打印"任务执行",主线程同时打印"主线程工作",实现并发:
python 复制代码
import time
from threading import Thread

def task():
    print("线程:任务执行中")
    time.sleep(2)  # 模拟任务耗时
    print("线程:任务完成")

if __name__ == '__main__':
    t = Thread(target=task)  # 绑定任务函数
    t.start()  # 启动线程
    print("主线程:正在处理其他工作")
    t.join()  # 等待线程结束,避免主线程先退出
  • 运行结果:先打印"主线程:正在处理其他工作"和"线程:任务执行中",2秒后打印"线程:任务完成"(体现并发)。
(2)带参线程(args/kwargs)
  • 核心逻辑 :用args传"位置参数"(元组形式,单个参数需加逗号),用kwargs传"关键字参数"(字典形式)。
  • (args传参):线程向指定用户打招呼:
python 复制代码
from threading import Thread

def say_hi(name):
    print(f"你好,{name}!")

t = Thread(target=say_hi, args=("小明",))  # args是元组,"小明"后加逗号
t.start()  # 输出:你好,小明!
  • (kwargs传参):线程打印指定用户的年龄:
python 复制代码
t = Thread(target=lambda name, age: print(f"{name}今年{age}岁"), 
           kwargs={"name": "小红", "age": 20})
t.start()  # 输出:小红今年20岁
(3)Timer定时器线程
  • 核心逻辑threading.Timer是Thread的子类,指定"延迟时间"后执行任务,可通过cancel()取消未执行的任务。
  • 例如:5秒后提醒喝水,若3秒内取消则不提醒:
python 复制代码
import threading
import time

def remind():
    print("该喝水了!")

# 创建定时器:延迟5秒执行remind
timer = threading.Timer(5, remind)
timer.start()

time.sleep(3)  # 主线程等待3秒
timer.cancel()  # 3秒后取消定时器,remind不会执行

三、线程间通信(queue模块,重点)

核心队列类型:解决线程安全的数据传递

1. Queue(先进先出,FIFO)
  • 核心逻辑 :多线程安全的队列,put()放入数据(队列满时阻塞),get()取出数据(队列空时阻塞),配合task_done()join()确保任务完成。
  • 例如(生产者-消费者模式):1个生产者做包子,2个消费者吃包子:
python 复制代码
from threading import Thread
from queue import Queue
import time

def producer(q):  # 生产者:10秒内做5个包子
    for i in range(5):
        q.put(f"包子{i}")
        print(f"生产者:做好了{q.qsize()}个包子")
        time.sleep(2)  # 每2秒做1个

def consumer(q, name):  # 消费者:吃包子
    while True:
        bun = q.get()
        print(f"{name}:吃掉了{bun}")
        q.task_done()  # 标记"这个包子已吃完"
        time.sleep(1)

if __name__ == '__main__':
    q = Queue(3)  # 队列最多存3个包子(避免堆太多)
    # 启动生产者和2个消费者
    Thread(target=producer, args=(q,)).start()
    Thread(target=consumer, args=(q, "消费者A")).start()
    Thread(target=consumer, args=(q, "消费者B")).start()
    q.join()  # 等待所有包子被吃完
    print("所有包子都吃完了!")
2. LifoQueue(后进先出,栈结构)
  • 通俗举例:叠盘子(最后放的盘子先拿):
python 复制代码
from queue import LifoQueue

q = LifoQueue()
q.put("盘子1")  # 先放盘子1
q.put("盘子2")  # 再放盘子2
print(q.get())  # 先拿盘子2(输出:盘子2)
print(q.get())  # 再拿盘子1(输出:盘子1)
3. PriorityQueue(优先级队列)
  • 核心逻辑 :元素以(优先级, 数据)形式放入,优先级数字越小,越先取出。
  • 例如:医院急诊(优先级1=病危,优先级2=病重,优先级3=普通):
python 复制代码
from queue import PriorityQueue

q = PriorityQueue()
q.put((3, "普通患者"))  # 优先级3
q.put((1, "病危患者"))  # 优先级1(最高)
q.put((2, "病重患者"))  # 优先级2

print(q.get()[1])  # 先处理病危患者(输出:病危患者)
print(q.get()[1])  # 再处理病重患者(输出:病重患者)
print(q.get()[1])  # 最后处理普通患者(输出:普通患者)

四、线程同步机制(解决线程安全问题,重点)

1. 锁(Lock/RLock)

(1)Lock(互斥锁)
  • 核心逻辑 :同一时刻仅1个线程能获取锁,未获取则阻塞;需成对使用acquire()(获取)和release()(释放),或用with自动管理(避免死锁)。
  • 例如(解决全局变量竞争):2个线程同时给计数器加1000次,无锁会出错,有锁则正确:
python 复制代码
from threading import Thread, Lock
import time

count = 0
lock = Lock()  # 创建互斥锁

def add():
    global count
    for _ in range(1000):
        with lock:  # 自动获取/释放锁,保护临界区
            count += 1  # 临界区:多线程共享操作

# 启动2个线程
t1 = Thread(target=add)
t2 = Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()

print(count)  # 输出:2000(无锁时可能小于2000)
(2)RLock(可重入锁)
  • 例如(嵌套锁场景):线程内嵌套调用加锁函数,RLock允许同一线程多次获取锁:
python 复制代码
from threading import RLock

rlock = RLock()

def func1():
    with rlock:
        print("执行func1")
        func2()  # 嵌套调用func2

def func2():
    with rlock:  # 同一线程可再次获取锁
        print("执行func2")

func1()  # 正常输出:执行func1 → 执行func2(若用Lock会死锁)

2. 信号量(Semaphore)

  • 核心逻辑 :控制"同时访问资源的线程数量",维护一个计数器,acquire()减1,release()加1,计数器为0时阻塞。
  • 例如(限制2个线程同时用打印机)
python 复制代码
from threading import Thread, Semaphore
import time

sem = Semaphore(2)  # 打印机最多允许2个线程同时使用

def use_printer(name):
    with sem:  # 自动获取/释放信号量
        print(f"{name}开始打印")
        time.sleep(3)  # 模拟打印耗时
        print(f"{name}打印完成")

# 5个线程竞争打印机
for i in range(5):
    Thread(target=use_printer, args=(f"用户{i}",)).start()
# 结果:始终只有2个用户在打印,其余等待

3. 事件(Event)

  • 核心逻辑 :通过内部"True/False"标志通信,set()设为True(唤醒所有等待线程),clear()设为False,wait()阻塞至标志为True。
  • 例如(老师发令后学生才跑步)
python 复制代码
from threading import Thread, Event
import time

event = Event()  # 初始标志为False

def student(name):
    print(f"{name}准备就绪,等待老师发令")
    event.wait()  # 阻塞至标志为True
    print(f"{name}开始跑步")

# 5个学生准备
for i in range(5):
    Thread(target=student, args=(f"学生{i}",)).start()

time.sleep(2)  # 老师准备2秒
event.set()  # 发令(标志设为True)
# 结果:2秒后所有学生同时开始跑步

五、线程池(concurrent.futures.ThreadPoolExecutor,重点)

1. 核心优势

  • 降低开销:重用线程,避免频繁创建/销毁线程;
  • 提高效率:无需等待线程创建,任务来了直接用空闲线程;
  • 便于管理:控制最大线程数,避免占用过多资源。

2. 2种核心使用方式

(1)submit():提交单个任务,返回Future对象(用result()获取结果)
  • 例如:用线程池计算5个数字的平方:
python 复制代码
from concurrent.futures import ThreadPoolExecutor

def square(num):
    return num * num

# 创建最多2个线程的线程池
with ThreadPoolExecutor(max_workers=2) as executor:
    # 提交5个任务
    futures = [executor.submit(square, i) for i in [1,2,3,4,5]]
    # 获取结果
    for future in futures:
        print(f"平方结果:{future.result()}")
# 输出:1、4、9、16、25
(2)map():批量提交任务,直接返回结果迭代器(按输入顺序输出)
  • 例如:用线程池批量下载3个文件(模拟耗时):
python 复制代码
from concurrent.futures import ThreadPoolExecutor
import time

def download(file):
    time.sleep(1)  # 模拟下载耗时
    return f"{file}下载完成"

with ThreadPoolExecutor(max_workers=2) as executor:
    files = ["视频1.mp4", "图片2.jpg", "文档3.pdf"]
    results = executor.map(download, files)  # 批量执行
    for res in results:
        print(res)
# 输出:视频1.mp4下载完成 → 图片2.jpg下载完成 → 文档3.pdf下载完成
相关推荐
Hello.Reader34 分钟前
Flink SQL 数据类型从 INT 到 VARIANT 的完整实战指南
python·sql·flink
再睡一夏就好36 分钟前
深入理解Linux程序加载:从ELF文件到进程地址空间的完整旅程
linux·运维·服务器·c++·学习·elf
f***686044 分钟前
MS SQL Server partition by 函数实战二 编排考场人员
java·服务器·开发语言
vi121231 小时前
ENVI 地形量化与植被指数反演
开发语言·python
rising start1 小时前
一、FastAPI入门
python·fastapi·端口
执笔者5481 小时前
网络编程:socket编程与两个简单的UdpServer练习
linux·服务器·网络·学习
闲人编程1 小时前
Flask应用工厂模式:构建可扩展的大型应用
后端·python·flask·工厂模式·codecapsule·应用工厂
python百炼成钢1 小时前
40.linux自带LED驱动
linux·运维·服务器
福尔摩斯张1 小时前
使用Linux命名管道实现无血缘关系进程间通信
linux·服务器·网络