一、多任务的概念
**什么是多任务:**多任务是指在同⼀时间内执⾏多个任务
我们之前所写的程序都是单任务的,也就是说⼀个函数或者⽅法执⾏完成 , 另外⼀个函 数或者⽅法才能执⾏。要想实现多个任务同时执⾏就需要使⽤多任务。
多任务的最⼤好处是充分利 ⽤CPU资源,提⾼程序的执⾏效率
多任务的两种表现形式:
① 并发 ② 并⾏
二、并发与并行
1、并发
概念:在⼀段时间内交替去执⾏多个任务。
-
多个任务在同一个时间段内交替执行
-
在单核CPU上通过时间片轮转实现任务切换
-
强调任务的分解和调度
例如:对于单核cpu处理多任务,操作系统轮流让各个任务交替执⾏,假如:软件1执⾏0.01秒,切换 到软件2,软件2执⾏0.01秒,再切换到软件3,执⾏0.01秒...... 这样反复执⾏下去, 实际上每个软 件都是交替执⾏的 . 但是,由于CPU的执⾏速度实在是太快了,表⾯上我们感觉就像这些软件都 在同时执⾏⼀样 . 这⾥需要注意单核cpu是并发的执⾏多任务的。
并发:任务数量大于CPU核心数
2、并⾏
**概念:**在⼀段时间内真正的同时⼀起执⾏多个任务。
-
多个任务在同一时刻真正同时执行
-
需要多核CPU支持
-
强调任务的同时执行
对于多核cpu处理多任务,操作系统会给cpu的每个内核安排⼀个执⾏的任务,多个内核是真正的 ⼀起同时执⾏多个任务。这⾥需要注意多核cpu是并⾏的执⾏多任务,始终有多个任务⼀起执⾏。
并行:任务数量小于或等于CPU核心数
3、并发与并行的区别
特性 | 并发 | 并行 |
---|---|---|
核心定义 | 多个任务在重叠的时间段内交替执行 | 多个任务在同一时刻同时执行 |
关键思想 | 任务切换 | 同时执行 |
硬件需求 | 单核CPU即可实现 | 必须依赖多核CPU或多台计算机 |
关注点 | 程序的设计与结构(处理多个任务的能力) | 程序的执行与计算(加快任务完成的速度) |
主要目的 | 提高系统的资源利用率 和响应能力 | 提高系统的计算速度 和吞吐量 |
关系 | 并发是并行的必要条件,但并发不一定并行。 并行是并发的真子集。 | |
生活比喻 | 一个人(单核)同时做两件事: 边看电视边回微信,大脑在快速切换。 | 两个人(多核)同时做两件事: 一个人看电视,另一个人同时回微信。 |
编程模型 | 多线程、协程、异步IO、事件循环 | 多进程、GPU计算、分布式计算 |
优势 | 使I/O密集型应用不会阻塞,最大化利用CPU时间片 | 充分利用多核资源,大幅缩短计算密集型任务的时间 |
挑战 | 线程安全、竞态条件、死锁、数据同步 | 任务拆分、负载均衡、进程间通信、数据一致性 |
三、进程与多进程
1、程序中实现多任务的⽅式
在Python中,想要实现多任务可以使⽤ 多进程来完成。
2、进程的概念
进程(Process)是资源分配的最⼩单位,它是操作系统进⾏资源分配和调度运⾏的基本单位,通 俗理解:⼀个正在运⾏的程序就是⼀个进程。
例如:正在运⾏的qq , 微信等 他们都是⼀个进程。
注: ⼀个程序运⾏后⾄少有⼀个进程
-
程序在执行时的一个实例(应用程序)
-
拥有独立的内存空间和系统资源
-
进程间相互隔离,安全性高
3、多进程的作⽤
图中是⼀个⾮常简单的程序 , ⼀旦运⾏hello.py这个程序 , 按照代码的执⾏顺序 , func_a函数执⾏ 完毕后才能执⾏func_b函数 . 如果可以让func_a和func_b同时运⾏ , 显然执⾏hello.py这个程序的 效率会⼤⼤提升 .
未使用多进程

使用了多进程

4、多进程完成多任务
① 导⼊进程包
import multiprocessing
② 通过进程类创建进程对象
进程对象 = multiprocessing.Process()
③ 启动进程执⾏任务
进程对象.start()
5、通过进程类创建进程对象
进程对象 = multiprocessing.Process([group [, target=任务名 [, name]]])
参数说明:
参数名 | 说明 |
---|---|
target | 执行的目标任务名,这里指的是函数名(方法名) |
name | 进程名,一般不用设置 |
group | 进程组,目前只能使用None |
6、进程创建与启动的代码
边听音乐,边敲代码:
python
# 导入多进程模块,用于创建和管理进程
import multiprocessing
# 导入时间模块,用于模拟耗时操作和计时
import time
def music():
"""任务函数:模拟听音乐"""
for i in range(3):
print('听音乐...')
time.sleep(0.2) # 模拟每次听音乐的耗时
def coding():
"""任务函数:模拟敲代码"""
for i in range(3):
print('敲代码...')
time.sleep(0.2) # 模拟每次敲代码的耗时
if __name__ == '__main__':
# 创建执行music函数的进程
music_process = multiprocessing.Process(target=music)
# 创建执行coding函数的进程
coding_process = multiprocessing.Process(target=coding)
# 启动两个进程
music_process.start()
coding_process.start()
# 等待两个进程都执行完毕
music_process.join()
coding_process.join()
print("所有任务完成!")

7、进程执⾏带有参数的任务
Process([group [, target [, name [, args [, kwargs]]]]])
参数说明:
参数名 | 说明 | 示例 |
---|---|---|
args | 以元组的方式给执行任务传参,表示调用对象的位置参数 | args=(1, 2, 'anne') |
kwargs | 以字典方式给执行任务传参,表示调用对象的关键字参数 | kwargs={'name': 'anne', 'age': 18} |
案例:args参数和kwargs参数的使⽤
python
# 导入多进程模块
import multiprocessing
# 导入时间模块
import time
def music(num):
"""
模拟听音乐的任务
参数:
- num: int, 音乐播放的次数
该函数通过循环模拟播放音乐,每次播放后暂停0.2秒。
"""
for i in range(num):
print('听音乐...')
time.sleep(0.2)
def coding(count):
"""
模拟编码任务
参数:
- count: int, 敲代码的次数
该函数通过循环模拟敲代码的动作,每次敲代码后暂停0.2秒。
"""
for i in range(count):
print('敲代码...')
time.sleep(0.2)
# 在主程序中启动多进程
if __name__ == '__main__':
# 创建音乐进程,参数为播放音乐的次数
music_process = multiprocessing.Process(target=music, args=(3,))
# 创建编码进程,参数为敲代码的次数
coding_process = multiprocessing.Process(target=coding, kwargs={'count': 3})
# 启动音乐进程
music_process.start()
# 启动编码进程
coding_process.start()
# 打印主进程开始的信息
print('主进程开始')
# 使主进程暂停1秒,以模拟进程初始化或资源准备的时间
time.sleep(1)
# 等待音乐进程结束
music_process.join()
# 等待编码进程结束
coding_process.join()
# 打印主进程结束的信息
print('主进程结束')

注意点:
-
入口点保护:必须使用 `if name == 'main':` 保护主程序入口
-
参数序列化:传递给进程的参数必须是可序列化的
-
进程间通信:使用 `Queue`、`Pipe` 等机制进行进程间数据交换
-
资源管理:及时调用 `join()` 等待进程结束,避免僵尸进程
四、线程与多线程
1、线程的概念
在Python中,想要实现多任务还可以使⽤多线程来完成。
-
进程内的执行单元(函数,class类)
-
同一进程内的线程共享内存空间(数据可以共享)
-
是CPU调度的基本单位
2、为什么使⽤多线程?
进程是分配资源的最⼩单位 , ⼀旦创建⼀个进程就会分配⼀定的资源 , 就像跟两个⼈聊QQ就需要 打开两个QQ软件⼀样是⽐较浪费资源的 .
线程是程序执⾏的最⼩单位 , 实际上进程只负责分配资源 , ⽽利⽤这些资源执⾏程序的是线程 , 也 就说进程是线程的容器 , ⼀个进程中最少有⼀个线程来负责执⾏程序 。
同时线程⾃⼰不拥有系统 资源,只需要⼀点⼉在运⾏中必不可少的资源,但它可与同属⼀个进程的其它线程共享进程所拥有 的全部资源 。这就像通过⼀个QQ软件(⼀个进程)打开两个窗⼝(两个线程)跟两个⼈聊天⼀样 , 实 现多任务的同时也节省了资源
3、多线程完成多任务
多线程完成多任务
① 导⼊线程模块
import threading
② 通过线程类创建线程对象
线程对象 = threading.Thread(target=任务名)
③ 启动线程执⾏任务
线程对象.start()
参数名 | 说明 |
---|---|
target | 执行的目标任务名,这里指的是函数名(方法名) |
name | 线程名,一般不用设置 |
group | 线程组,目前只能使用None |
线程创建与启动代码
单线程案例
python
# 单线程案例
import time
def music():
for i in range(3):
print('听音乐...')
time.sleep(0.2)
def coding():
for i in range(3):
print('敲代码...')
time.sleep(0.2)
if __name__ == '__main__':
music()
coding()

多线程案例
python
# 多线程案例
import time
import threading
def music():
"""
模拟听音乐的任务
循环三次,每次打印听音乐的状态并暂停0.2秒
"""
for i in range(3):
print('听音乐...')
time.sleep(0.2)
def coding():
"""
模拟编码的任务
循环三次,每次打印敲代码的状态并暂停0.2秒
"""
for i in range(3):
print('敲代码...')
time.sleep(0.2)
if __name__ == '__main__':
# 创建一个线程,用于播放音乐。通过threading.Thread类创建线程,music函数作为线程要执行的目标。
music_thread = threading.Thread(target=music)
# 创建一个线程,用于编码工作。同样使用threading.Thread类创建,coding函数作为线程要执行的目标。
coding_thread = threading.Thread(target=coding)
# 启动音乐线程和编码线程
music_thread.start()
coding_thread.start()
# 等待音乐线程完成其任务
music_thread.join()
# 等待编码线程完成其任务
coding_thread.join()
print('程序结束')

线程执⾏带有参数的任务
参数名 | 说明 |
---|---|
args | 以元组的方式给执行任务传参 |
kwargs | 以字典方式给执行任务传参 |
python
# 导入time模块和threading模块
import time
import threading
def music(num):
"""
模拟听音乐的函数
参数:
- num: int, 控制音乐播放的次数
该函数通过循环播放音乐,并在每次播放时暂停0.2秒来模拟听音乐的行为。
"""
for i in range(num):
print('听音乐...')
time.sleep(0.2)
def coding(count):
"""
模拟编码的函数
参数:
- count: int, 控制编码操作的次数
该函数通过循环模拟编码行为,并在每次编码时暂停0.2秒来模拟实际编码过程。
"""
for i in range(count):
print('敲代码...')
time.sleep(0.2)
# 程序入口
if __name__ == '__main__':
# 创建一个线程,负责播放音乐
# 参数说明:
# - target: 指定要执行的函数,这里是音乐播放函数
# - args: 传递给函数的参数,这里使用元组(3,)作为示例
music_thread = threading.Thread(target=music, args=(3,))
# 创建另一个线程,负责编码任务
# 参数说明:
# - target: 指定要执行的函数,这里是编码函数
# - kwargs: 传递给函数的关键字参数,这里指定count为3
coding_thread = threading.Thread(target=coding, kwargs={'count': 3})
# 启动音乐线程和编码线程
music_thread.start()
coding_thread.start()
# 等待音乐线程完成其任务
music_thread.join()
# 等待编码线程完成其任务
coding_thread.join()
print('程序结束')

注意点
-
GIL限制:受全局解释器锁限制,CPU密集型任务无法真正并行
-
线程安全:共享数据时需要使用锁机制保证线程安全
-
死锁风险:多个锁的使用需要注意死锁问题
-
资源竞争:需要合理使用同步原语(Lock、RLock、Semaphore等)
五、协程
1 协程定义与优势
协程(Coroutine)是⽤户态的轻量级线程,通过协作式多任务实现并发。
相⽐线程,协程的切换⽆需操 作系统调度,仅需保存寄存器上下⽂,因此效率更⾼。
核⼼优势:
-
⽆锁机制:避免多线程同步开销
-
⾼并发:单线程内处理数千级I/O密集型任务(如⽹络请求)
-
代码简洁:⽤同步语法写异步逻辑( async/await )
2 协程实现⽅式(以 asyncio 为核⼼)
基础语法
python
import asyncio
# 定义一个异步协程函数
async def my_coroutine():
"""
一个简单的协程函数,用于演示asyncio库的基本使用。
此函数没有参数和返回值。
"""
print("Start")
await asyncio.sleep(1) # 非阻塞式休眠
print("End")
# 运行协程
asyncio.run(my_coroutine()) # Python3.7+ 推荐方式

事件循环与任务创建
python
import asyncio
# 定义一个异步任务,该任务会在指定延迟后完成
async def task(name, delay):
"""
异步任务函数,模拟一个需要时间完成的任务。
参数:
name: 任务名称,用于标识任务。
delay: 延迟时间,任务完成前需要等待的时间(秒)。
"""
await asyncio.sleep(delay) # 模拟任务耗时
print(f"{name} completed")
# 定义主协程,用于管理和执行其他协程任务
async def main():
"""
主协程函数,负责调度和执行多个异步任务。
"""
# 创建任务列表
tasks = [
asyncio.create_task(task("A", 2)), # 创建任务A,延迟2秒完成
asyncio.create_task(task("B", 1)) # 创建任务B,延迟1秒完成
]
await asyncio.gather(*tasks) # 并发执行所有任务
# 运行主协程
asyncio.run(main())

注:输出顺序:B → A(任务按完成时间排序)
3 核⼼模块与API
模块/方法 | 作用描述 |
---|---|
asyncio.run() | 启动事件循环的入口函数 |
asyncio.create_task() | 将协程包装为任务对象 |
asyncio.gather() | 并发执行多个协程 |
asyncio.sleep() | 非阻塞式等待(替代 time.sleep) |
asyncio.Queue | 协程安全队列,用于生产者-消费者模型 |
4 其他协程库
- Gevent
基于 greenlet ,通过 monkey.patch_all() ⾃动切换协程:
python
# 导入gevent的monkey模块,用于打补丁以支持协程
from gevent import monkey
# 导入gevent模块,用于创建协程
import gevent
# 为所有标准模块打上补丁,使其支持协程
# 包括socket、select、thread、time等模块的阻塞操作变为非阻塞
monkey.patch_all()
# 定义一个简单的任务函数,将在协程中执行
def task():
print("Gevent task")
# 使用列表推导式创建3个协程任务
# gevent.spawn() 创建协程实例,每个实例执行task()函数
tasks = [gevent.spawn(task) for _ in range(3)]
# 一次性等待所有协程完成
# joinall()会阻塞当前协程直到所有任务完成
gevent.joinall(tasks)
- Greenlet ⼿动切换协程:
python
# 导入greenlet模块,实现轻量级协程
from greenlet import greenlet
# 定义第一个协程函数
def test1():
print(1)
gr2.switch() # 手动切换到gr2协程
# 定义第二个协程函数
def test2():
print(2)
# test2执行完毕后自动回到主协程
# 创建协程对象
gr1 = greenlet(test1) # 基于test1函数创建协程
gr2 = greenlet(test2) # 基于test2函数创建协程
# 启动协程系统
gr1.switch() # 切换到gr1协程,开始执行test1函数
注意点:
-
异步环境:协程必须在异步环境中运行
-
await关键字:调用异步函数时必须使用 `await` 关键字
-
阻塞操作:避免在协程中使用阻塞操作,应使用异步版本
-
异常处理:合理处理协程中的异常,避免影响整个事件循环
-
资源管理:正确管理协程的生命周期,及时取消不需要的任务
六、进程 vs. 线程 vs. 协程 总结对比表
性 | 进程 (Process) | 线程 (Thread) | 协程 (Coroutine) |
---|---|---|---|
基本定义 | 资源分配的基本单位, 程序的一次执行实例 | CPU调度的基本单位, 进程内的一个执行流 | 用户态的轻量级线程, 由程序员控制的协作式任务 |
资源开销 | 大 (独立内存空间、PCB) | 中等 (共享内存,但有独立栈、TCB) | 极小 (通常在KB级别,无系统开销) |
切换开销 | 大 (需要切换内存空间,CPU上下文) | 中等 (需切换CPU上下文,由OS内核调度) | 极小 (用户态切换,无内核参与) |
数据共享 | 复杂 (需要IPC:队列、管道、共享内存等) | 简单 (共享进程内存,但需注意线程安全) | 极简单 (共享所有上下文变量) |
并发能力 | 利用多核CPU (真正并行) | 利用多核CPU (真正并行) | 单线程内并发 (协作式并发) |
创建数量 | 少(通常几十到上百个) | 中(通常几百到上千个) | 极多(可轻松创建数万甚至百万个) |
隔离性/安全性 | 高 (一个进程崩溃不影响其他进程) | 低 (一个线程崩溃可能导致整个进程崩溃) | 高 (一个协程异常通常不影响其他协程) |
控制者 | 操作系统内核 | 操作系统内核 | 程序员(用户态) |
适用场景 | CPU密集型 任务、 需要高安全隔离的任务 | I/O密集型 任务、 需要共享数据的并发任务 | 高并发I/O 密集型任务、 大量网络连接、异步编程 |
编程复杂度 | 高(需处理进程间通信) | 中(需处理线程同步和锁) | 低(异步编程模型,逻辑清晰) |
Python模块 | multiprocessing |
threading |
asyncio |
全局解释器锁(GIL)影响 | 无影响(每个进程有独立GIL) | 受限制(同一进程线程只能有一个执行) | 受限制(在同一个线程内运行) |