Python3 多线程详解

Python 的多线程就像一个工厂里的多条生产线 —— 同一个厂房(进程)里,多条生产线(线程)可以同时干活,共享厂房里的工具(内存资源),大大提高工作效率。今天咱们用大白话详解 Python3 的多线程,从基础到实战,看完就能用。

一、先搞懂:什么是线程?和进程有啥区别?

假设你打开一个微信(这是一个进程),微信里同时进行着:

  • 接收消息(一个任务)
  • 下载图片(另一个任务)
  • 播放语音(第三个任务)

这些同时运行的 “小任务” 就是线程

线程 vs 进程

  • 进程是 “独立的工厂”,每个进程有自己的内存空间(比如微信和 QQ 是两个进程,内存不共享);
  • 线程是 “工厂里的生产线”,属于同一个进程,共享内存(微信里的接收消息和下载图片线程,共享微信的内存数据);
  • 线程更 “轻量”:启动 / 切换线程比启动 / 切换进程快得多,适合做 “同时处理多个小任务” 的场景。

二、Python 多线程的 “工具箱”:threading 模块

Python3 通过内置的threading模块实现多线程,替代了旧版的thread模块。最核心的是Thread类,用它就能创建线程。

1. 创建线程的两种方式

方式一:直接传 “要执行的任务”(函数)
先定义一个普通函数作为 “任务”,然后用Thread包装起来启动:
 
import threading
import time

# 定义一个任务函数:打印数字
def print_numbers(name):
    for i in range(5):
        print(f"线程{name}{i}")
        time.sleep(0.5)  # 模拟任务耗时

# 创建两个线程,分别执行print_numbers任务
t1 = threading.Thread(target=print_numbers, args=("A",))  # args是传给函数的参数
t2 = threading.Thread(target=print_numbers, args=("B",))

# 启动线程(开始干活)
t1.start()
t2.start()

# 等待线程结束(主线程等子线程干完再走)
t1.join()
t2.join()

print("所有线程执行完毕!")
 

运行后会看到 A 和 B 线程交替打印,说明它们在 “同时” 工作。
方式二:继承 Thread 类,重写 run () 方法
如果任务逻辑复杂,可以把线程封装成一个类:
 
 
import threading
import time

# 继承Thread类
class MyThread(threading.Thread):
    # 重写run()方法:线程启动后会自动执行这里的代码
    def run(self):
        for i in range(5):
            print(f"线程{self.name}{i}")  # self.name是线程名(可自定义)
            time.sleep(0.5)

# 创建线程(可以指定name参数给线程起名)
t1 = MyThread(name="A")
t2 = MyThread(name="B")

t1.start()
t2.start()
t1.join()
t2.join()

print("所有线程执行完毕!")
 

两种方式效果一样,实际开发中第一种更简洁(适合简单任务),第二种更灵活(适合复杂逻辑)。

2. 线程的核心操作

  • start():启动线程(让线程开始执行任务);
  • join():让主线程 “等待” 该线程执行完毕后再继续(比如上面的例子,主线程会等 t1 和 t2 都跑完才打印 “所有线程执行完毕”);
  • is_alive():判断线程是否还在运行(返回 True/False);
  • name:线程的名字(可通过Thread(name="xxx")设置,方便调试);
  • daemon:守护线程(后台线程)。如果设置daemon=True,主线程结束时,不管守护线程是否执行完都会被强制终止(适合日志、监控等 “不重要” 的后台任务)。

示例:守护线程
 
 
import threading
import time

def background_task():
    while True:
        print("后台监控中...")
        time.sleep(1)

# 创建守护线程(daemon=True)
t = threading.Thread(target=background_task, daemon=True)
t.start()

# 主线程只运行3秒
time.sleep(3)
print("主线程结束,守护线程会被强制终止")
 

运行后会看到:主线程 3 秒后结束,守护线程的 “监控” 也会跟着停。

三、坑点:线程安全问题(多个线程抢资源怎么办?)

多个线程共享数据时,可能会出问题。比如两个线程同时给一个变量 “加 1”,结果可能不对:

import threading

count = 0  # 共享变量

def add():
    global count
    for _ in range(100000):
        count += 1  # 看似简单的加1,实际是"读-改-写"三步操作

# 两个线程同时执行add
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()

print(f"最终结果:{count}")  # 预期200000,实际可能小于200000
 

原因:当 t1 刚读完 count 的值(比如 100),还没来得及改,t2 也读到了 100,两者都加 1 后写回,结果本该是 102,却变成了 101—— 这就是 “竞态条件”(多个线程抢资源导致数据错乱)。

解决:用 “锁”(Lock)保护共享资源

就像厕所需要锁一样,共享资源也需要 “锁”:一个线程用的时候上锁,用完了解锁,其他线程必须等解锁后才能用。
 
 
import threading

count = 0
lock = threading.Lock()  # 创建一把锁

def add():
    global count
    for _ in range(100000):
        lock.acquire()  # 上锁(如果被占用,就等待)
        count += 1
        lock.release()  # 解锁(让其他线程可以用)

t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
t1.start()
t2.start()
t1.join()
t2.join()

print(f"最终结果:{count}")  # 这次一定是200000
 

注意

  • 锁不要滥用,否则会让多线程变成 “串行”(和单线程一样慢);
  • 锁要配对使用(acquire 后必须 release),否则会导致 “死锁”(所有线程都在等对方解锁,卡死)。

四、Python 多线程的 “特殊限制”:GIL 全局解释器锁

这是 Python 多线程最容易踩的坑:在 CPU 密集型任务中,多线程可能比单线程还慢

原因是 Python 有个 “GIL(全局解释器锁)”:不管你开多少线程,同一时间只有一个线程能执行 Python 字节码(相当于一个工厂只有一个总开关,同一时间只能开一条生产线)。

  • IO 密集型任务(比如网络请求、文件读写):多线程很有用!因为 IO 操作时线程会释放 GIL(等待数据时不占着开关),其他线程可以干活;
  • CPU 密集型任务(比如大量计算):多线程没用!因为线程一直在抢 GIL,切换还会浪费时间,这时应该用multiprocessing多进程(绕过 GIL 限制)。

五、线程间通信:用 Queue 安全传数据

线程间尽量不要直接操作共享变量(容易出安全问题),推荐用queue.Queue(线程安全的队列)传递数据:
 
import threading
import queue
import time

# 生产者线程:往队列里放数据
def producer(q):
    for i in range(5):
        data = f"数据{i}"
        q.put(data)  # 放数据
        print(f"生产了:{data}")
        time.sleep(1)
    q.put(None)  # 放一个"结束信号"

# 消费者线程:从队列里拿数据
def consumer(q):
    while True:
        data = q.get()  # 拿数据(如果队列空,会等待)
        if data is None:
            break  # 收到结束信号,退出
        print(f"消费了:{data}")
        time.sleep(2)

# 创建一个线程安全的队列
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()

print("完成")

队列的put()get()方法自带锁机制,不用手动加锁,安全又方便。

六、总结:多线程该怎么用?

  1. 适用场景:IO 密集型任务(爬虫、文件处理、网络请求等);
  2. 避坑点
    • 多线程共享数据必须加锁(Lock);
    • CPU 密集型任务优先用多进程(multiprocessing);
    • 线程通信优先用queue.Queue,少用共享变量;
  3. 核心工具threading.Thread创建线程,start()启动,join()等待,Lock保证安全,Queue传递数据。

posted on 2025-11-12 08:39  小陶coding  阅读(63)  评论(0)    收藏  举报