Python 多线程编程从入门到精通:基础 + 实战 + 避坑全攻略

前言

在 Python 开发中,多线程是提升程序效率、实现并发任务的核心技术,尤其适合 IO 密集型场景(文件读写、网络请求、爬虫等)。这篇文章从零带你吃透 Python 多线程:基础用法、线程通信、线程池、GIL 锁原理、常见问题解决方案,全程附带可直接运行的实战代码,新手也能轻松上手。

一、多线程核心概念

1. 什么是多线程?

多线程是同一个进程内 同时运行多个独立执行的线程,多个线程共享进程的内存、文件等资源,每个线程拥有独立的执行栈和调度权,能实现任务的并发执行

简单说:一个程序(进程)可以同时干多件事,就是多线程。

1.1 多线程 VS 单线程

  • 单线程:程序只有一条执行流,任务按顺序执行,逻辑简单,但无法利用多核 CPU,遇到阻塞任务会导致程序卡顿。场景:简单的顺序脚本、无需并发的小工具。
  • 多线程:多条执行流同时运行,能充分利用 CPU 空闲时间,提升程序响应速度和并发能力。场景:爬虫、文件批量下载、接口并发请求、GUI 程序防卡顿。

1.2 Python 多线程的核心模块

Python 内置 threading 模块实现多线程编程,无需额外安装依赖,是 Python 多线程开发的标准工具。

二、threading 模块:线程的创建与基础管理

threading.Thread 是创建线程的核心类,我们先掌握线程的创建、启动、等待、标识四大基础操作。

2.1 基础创建线程

指定目标函数,创建线程对象,这是最常用的创建方式。

python 复制代码
import threading

# 定义线程要执行的任务函数
def print_info():
    for i in range(3):
        print(f"子线程运行:{i}")

# 创建线程:target=任务函数,不要加括号!
thread = threading.Thread(target=print_info)

2.2 启动线程:start () 方法

调用 start() 才会真正启动线程,操作系统调度线程执行任务。

python 复制代码
# 启动线程
thread.start()
# 主线程继续执行
print("主线程运行中...")

2.3 线程阻塞等待:join () 方法

join() 会让主线程等待子线程执行完毕后再继续,实现线程同步。

python 复制代码
# 等待子线程执行完成
thread.join()
print("子线程执行完毕,主线程继续!")

2.4 线程标识:name/ident 属性

给线程命名、获取唯一 ID,方便多线程调试和管理。

python 复制代码
# 创建线程时指定名称
thread = threading.Thread(target=print_info, name="测试线程")
thread.start()

# 获取线程名称和唯一标识符
print("线程名称:", thread.name)
print("线程ID:", thread.ident)

三、线程间通信与同步:解决数据安全问题

多线程共享进程资源,多个线程同时修改共享数据会引发数据错乱,必须通过同步机制保证线程安全。

3.1 Lock 互斥锁:保证共享数据安全

threading.Lock 是最基础的互斥锁,同一时间只有一个线程能获取锁,其他线程等待,避免竞态条件。

推荐使用 with 语法,自动加锁 / 解锁,无需手动释放。

python 复制代码
import threading

# 共享变量
total = 0
# 创建互斥锁
lock = threading.Lock()

def add_num():
    global total
    # 加锁:with语法自动管理锁
    with lock:
        for _ in range(100000):
            total += 1

# 创建5个线程
threads = [threading.Thread(target=add_num) for _ in range(5)]
# 启动所有线程
for t in threads:
    t.start()
# 等待所有线程完成
for t in threads:
    t.join()

print("最终结果:", total)  # 结果一定是500000,线程安全

3.2 Queue 队列:线程间安全通信

queue.Queue线程安全的队列,专门用于线程间数据传递,内置锁机制,无需手动加锁。

python 复制代码
import threading
import queue

# 生产者:向队列放数据
def producer(q):
    for i in range(5):
        q.put(f"数据{i}")
        print(f"生产:数据{i}")

# 消费者:从队列取数据
def consumer(q):
    while not q.empty():
        data = q.get()
        print(f"消费:{data}")

# 创建队列
q = queue.Queue()
# 创建线程
t1 = threading.Thread(target=producer, args=(q,))
t2 = threading.Thread(target=consumer, args=(q,))
# 启动线程
t1.start()
t2.start()
# 等待完成
t1.join()
t2.join()

优势:

  1. 自动管理线程创建 / 销毁,降低资源消耗
  2. 控制并发数量,避免线程过多导致程序崩溃
  3. 支持获取任务返回值,使用更灵活

四、线程池:高效管理大量线程

手动创建大量线程会浪费系统资源,线程池可以复用线程、控制最大并发数,是企业开发的首选方案。

Python 内置 concurrent.futures.ThreadPoolExecutor 实现线程池。

python 复制代码
from concurrent.futures import ThreadPoolExecutor
import time

# 定义任务函数
def task(name):
    print(f"任务{name}开始执行")
    time.sleep(2)
    return f"任务{name}执行完成"

# 创建线程池:max_workers=最大线程数
with ThreadPoolExecutor(max_workers=3) as executor:
    # 提交任务到线程池
    future1 = executor.submit(task, 1)
    future2 = executor.submit(task, 2)
    future3 = executor.submit(task, 3)

    # 获取任务返回结果
    print(future1.result())
    print(future2.result())
    print(future3.result())

五、必知:全局解释器锁(GIL)对多线程的影响

很多新手会疑惑:Python 多线程为什么不能充分利用多核 CPU? 核心原因就是 GIL(全局解释器锁)

5.1 GIL 是什么?

GIL 是 CPython 解释器的互斥锁 ,它的规则:同一时刻,同一个进程内只有一个线程能执行 Python 字节码

也就是说:Python 多线程是并发 (交替执行),不是并行(同时执行)。

5.2 GIL 对程序的影响

  1. 计算密集型任务 :多线程效率极低,GIL 会成为性能瓶颈,推荐用多进程替代。
  2. IO 密集型任务:多线程效率大幅提升!因为线程在等待 IO(网络、文件)时,会自动释放 GIL,其他线程可以执行。

5.3 解决方案

  • IO 密集型:继续用多线程(threading / 线程池)
  • 计算密集型:使用 multiprocessing 多进程
  • 高性能异步:使用 asyncio 异步编程

六、多线程常见问题与解决方案

多线程编程最容易踩坑,掌握这两个高频问题,开发少走弯路。

6.1 死锁(Deadlock):线程互相等待锁

死锁原因

两个线程各自持有一个锁,同时等待对方释放锁,导致程序无限阻塞。

示例(死锁代码):

python 复制代码
import threading
lockA = threading.Lock()
lockB = threading.Lock()

def func1():
    lockA.acquire()
    lockB.acquire()
    lockB.release()
    lockA.release()

def func2():
    lockB.acquire()
    lockA.acquire()
    lockA.release()
    lockB.release()

# 运行后程序卡死,发生死锁
threading.Thread(target=func1).start()
threading.Thread(target=func2).start()

死锁解决方法

  1. 统一锁的获取顺序(最常用)
  2. 使用 timeout 超时机制
  3. 避免嵌套锁

修复死锁后的代码:

python 复制代码
# 统一先获取lockA,再获取lockB
def func1():
    with lockA:
        with lockB:
            pass

def func2():
    with lockA:
        with lockB:
            pass

6.2 守护线程:程序退出自动关闭子线程

默认情况下,主线程退出后,子线程会继续执行。如果希望主线程退出,子线程直接关闭,设置守护线程。

python 复制代码
import threading
import time

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

# 设置 daemon=True 成为守护线程
t = threading.Thread(target=daemon_task, daemon=True)
t.start()

time.sleep(3)
print("主线程退出,守护线程自动关闭")
相关推荐
神仙别闹2 小时前
基于Python实现(控制台)个人信息系统
开发语言·python
a9511416422 小时前
c++如何解析二进制协议中的可选字段读取逻辑及其反序列化【详解】
jvm·数据库·python
曾阿伦2 小时前
AES 加密解密详解及示例
python·加密解密
Hello eveybody2 小时前
介绍最大公因数和最小公约数(Python)
开发语言·python
weixin_580614002 小时前
golang如何实现时间格式化_golang时间格式化方法详解
jvm·数据库·python
forEverPlume2 小时前
c++怎么利用std--span实现在不拷贝数据的前提下解析大规模文件【进阶】
jvm·数据库·python
Ulyanov2 小时前
《PySide6 GUI开发指南:QML核心与实践》 第十篇:综合实战——构建完整的跨平台个人管理应用
开发语言·python·qt·ui·交互·qml·雷达电子战系统仿真
aq55356002 小时前
数字资源分发的技术革命与未来趋势
java·开发语言·python·php
AI玫瑰助手2 小时前
Python基础:元组的定义与不可变特性(对比列表)
开发语言·python·信息可视化