一、概念
多线程(threading):同一进程里开多条“轻量级执行路线”,大家共享同一块内存。
多进程(multiprocessing):启动多个独立 Python 解释器,彼此内存隔离,靠队列/管道通信。
二、多线程和多进程的区别
对比点 | 多线程 | 多进程 |
内存 | 共享 | 独立 |
开销 | 启动快、占内存小 | 启动慢、占内存大 |
CPU并行 | 受GIL限制 | 不受GIL限制,能多核并行 |
适用任务 | I/O密集型(如网络、文件) | CPU密集型(如计算) |
通信 | 直接读写变量(需加锁) | Queue / Pipe / Manager |
调试难度 | 死锁、竞态难抓 | 进程崩溃互不影响,但调试也麻烦 |
三、多线程基本语法
3.1 导包
import threading
3.2 Thread类
threading
模块里提供了一个类叫 Thread
(注意大写 T)
这个类就是“线程模具”——你拿它就能“印”出一条新线程
3.3 Thread类中的关键参数
参数名 | 类型/示例 | 作用 |
target | 可调用对象,如 | 线程启动后要执行的函数/方法 |
args | 元组 ,如 | 传给 |
kwargs | 字典 ,如 | 传给 |
name | 字符串 ,如 | 给线程起名字,方便调试 |
daemon |
| 设为 |
四、多进程基本语法
4.1 导包
import multiprocessing
4.2 Process和Pool 类
Process 类 :一条一条手动开进程
Pool 类:进程池,一行代码并行 map
4.3 Process和Pool的关键参数
4.3.1 Process的关键参数
from multiprocessing import Process, Lock, Value
p = Process(
group=None, # 预留,不用管
target=None, # 【必填】子进程要执行的函数
args=(), # 给 target 的位置参数(元组)
kwargs={}, # 给 target 的关键字参数(字典)
name=None, # 子进程名字,方便调试
daemon=False # True 时主进程结束立刻杀子进程
)
90 % 场景只需写:
p = Process(target=func, args=(x, y))
4.3.2 Pool的关键参数
from multiprocessing import Pool
with Pool(
processes=None, # 工作进程数,None = 用 CPU 核数
initializer=None, # 每个进程启动时先跑一次的初始化函数
initargs=(), # initializer 的位置参数
maxtasksperchild=None # 每个子进程做完多少任务后重启,防内存泄漏
) as pool:
pool.map(func, iterable)
常用 1 行写法:Pool()
或 Pool(4)
直接开 4 个进程
五、多线性和多进程代码举例(Python3)
5.1 多线程最小例子
import threading # 导入线程模块,提供 Thread、Lock 等工具
# 线程要执行的函数:打印 n 次问候,并标明当前线程名字
def say(n):
"""
n: int
需要打印的次数
"""
for i in range(n):
# threading.current_thread().name 获取当前线程的名字
print('hi from', threading.current_thread().name)
# 创建两条线程对象:
# target=say 告诉线程启动后去执行 say 函数
# args=(3,) 把元组 (3,) 作为位置参数传给 say,因此 say 将收到 n=3
t1 = threading.Thread(target=say, args=(3,))
t2 = threading.Thread(target=say, args=(2,))
# 启动线程:真正开始并发执行
t1.start() # 子线程 1 开始跑 say(3)
t2.start() # 子线程 2 开始跑 say(2)
# 等待线程结束:阻塞主线程,直到 t1、t2 都跑完
t1.join()
t2.join()
# 所有子线程结束后主线程继续
print('main done')
5.2 多进程最小例子
# -*- coding: utf-8 -*-
"""
多进程最小示例:创建两条子进程并发打印问候语
运行环境:Windows / Linux / macOS
注意:Windows 下必须放在 if __name__ == '__main__': 中
"""
import multiprocessing as mp # 导入 multiprocessing 模块,简写为 mp
# 子进程要执行的函数
# 这个函数必须“顶层可导入”,否则子进程会找不到
def say(n: int):
"""
n : int
当前进程需要打印的次数
"""
# mp.current_process().name 获取当前子进程的名字(默认形如 "Process-1")
for i in range(n):
print('hi from', mp.current_process().name)
# ---------- 主进程入口 ----------
if __name__ == '__main__':
# 创建两条进程对象:
# target=say 子进程启动后去执行 say 函数
# args=(3,) 把位置参数 3 传给 say,因此子进程 1 会打印 3 次
p1 = mp.Process(target=say, args=(3,), name='Worker-1') # 可自定义名字
p2 = mp.Process(target=say, args=(2,), name='Worker-2')
# 启动子进程:真正开始并行(操作系统层面)
p1.start() # 子进程 1 开始跑 say(3)
p2.start() # 子进程 2 开始跑 say(2)
# 阻塞主进程,等待两条子进程都结束
p1.join()
p2.join()
# 所有子进程退出后,主进程才继续
print('main done')
六、线程锁和进程锁
# -*- coding: utf-8 -*-
"""
演示「线程锁」和「进程锁」的共享计数示例。
主程序未写出,这里只给出函数与锁定义,方便后续调用。
"""
import threading # 线程相关
import multiprocessing # 进程相关
import time # 仅作演示延时(可删)
# ----------------- 锁对象 -----------------
# 1. 线程锁:保护多线程共享变量
lock1 = threading.Lock()
# 2. 进程锁:保护多进程共享变量(Value / Array / Manager)
lock2 = multiprocessing.Lock()
# ----------------- 计数函数 -----------------
def counter(lock, num, cnt):
"""
在并发环境下安全地把计数器 cnt 累加 num 次。
参数
----
lock : threading.Lock 或 multiprocessing.Lock
用于互斥,防止同时修改计数器。
num : int
累加次数。
cnt : int(线程) 或 multiprocessing.Value('i', 0)(进程)
共享计数器。线程可直接用全局 int;进程必须借助 Value/Array/Manager。
"""
for _ in range(num):
with lock: # 自动 acquire / release
cnt.value += 1 # 对 Value 对象取值/赋值用 .value
# 如果是线程共享的普通 int,直接 cnt += 1 即可
# time.sleep(0) # 可加极短 sleep 放大并发冲突,方便实验