多任务概述
多任务的核心优势在于充分利用 CPU 资源,从而大幅提升程序执行效率。在单任务模式下,一个函数或方法必须执行完毕,下一个才能开始;而多任务允许我们在同一时间内(或宏观上交替)执行多个任务。
操作系统层面的多任务通常有两种表现形式:
- 并发:在一段时间内,系统交替执行多个任务,给人同时运行的感觉。
- 并行:在一段时间内,真正的多个任务同时一起执行(通常需要多核支持)。

进程(Process)
基本概念
进程是 CPU 资源分配的最小单位,也是操作系统进行资源调度运行的基本实体。通俗来说,正在运行的程序就是一个进程。例如,当前打开的 QQ、微信等应用,每一个都是一个独立的进程。
注意:一个程序运行后至少包含一个进程。
为什么需要多进程?
假设有一个简单的脚本 hello.py,其中包含两个函数 func_a 和 func_b。默认情况下,代码按顺序执行:func_a 跑完才轮到 func_b。如果能让它们同时运行,程序的整体效率显然会更高。

创建进程的步骤
在 Python 中实现多任务,主要依赖 multiprocessing 模块。
-
导入工具包
import multiprocessing -
实例化进程对象 使用
multiprocessing.Process类创建子进程对象。关键参数如下:target:子进程要执行的任务(通常是回调函数)。args:以元组形式传递参数,顺序需与函数定义一致。kwargs:以字典形式传递参数,key 需与函数参数名一致。name:子进程的名称,便于调试。
-
启动进程 调用
process_object.start()来启动任务。
代码示例:一边敲代码一边听音乐
下面是一个模拟多任务的经典案例,展示如何同时执行两个独立任务。
""" 使用多进程 模拟一边敲代码,一边听音乐 """
import multiprocessing
import time
def coding():
for i in range(3):
print("I'm coding")
time.sleep(0.2)
def music():
for i in range(3):
print("I'm music...")
time.sleep(0.2)
if __name__ == '__main__':
# 通过进程类创建进程对象
p1 = multiprocessing.Process(target=coding)
p2 = multiprocessing.Process(target=music)
# 启动进程
p1.start()
p2.start()
带参数的任务处理
实际开发中,任务往往需要接收参数。我们可以灵活使用 args 或 kwargs。
""" 进程带参数的任务 """
import multiprocessing
import time
def coding(num, name):
for i in range(num):
print(f"{name}在写第{i}行代码")
time.sleep(0.2)
def music(num, name):
for i in range(num):
print(f"{name}在听第{i}首音乐")
time.sleep(0.2)
if __name__ == '__main__':
# args 传参:顺序必须匹配
p1 = multiprocessing.Process(target=coding, args=(3, "小王"))
# kwargs 传参:key 必须匹配参数名
p2 = multiprocessing.Process(target=music, kwargs={"num": 7, "name": "大名"})
p1.start()
p2.start()
进程编号与管理
每个进程都有唯一的 ID,方便我们区分主进程和子进程,验证父子关系。
- 获取当前进程 ID:
os.getpid() - 获取父进程 ID:
os.getppid()

进程间的隔离性
这是多进程与多线程最大的区别之一:进程之间不共享全局变量。
子进程实际上是父进程的副本,它会拷贝父进程的资源(包括全局变量)。因此,不同进程修改各自的全局变量互不影响。
""" 进程之间数据是相互隔离的 """
import multiprocessing
import time
my_list = []
def write_data():
for i in range(3):
my_list.append(i)
print("add:", i)
print("write_data:", my_list)
def read_data():
print("read_data:", my_list)
if __name__ == '__main__':
p1 = multiprocessing.Process(target=write_data)
p2 = multiprocessing.Process(target=read_data)
p1.start()
time.sleep(1)
p2.start()

主进程与子进程的等待机制
默认情况下,主进程会等待所有子进程执行完毕后才会退出。这意味着即使主逻辑已经跑完,只要子进程还在忙,程序就不会结束。
""" 默认情况下,主进程会等待子进程执行结束再结束 """
import multiprocessing
import time
def work():
for i in range(10):
print("work:", i)
time.sleep(0.2)
if __name__ == '__main__':
work_process = multiprocessing.Process(target=work)
work_process.start()
time.sleep(1)
print("主进程结束")
如果不希望主进程等待,可以设置子进程为守护进程(Daemon)。当主进程退出时,守护进程会被自动销毁,不再等待其完成。
""" 不让主进程等待子进程 """
import multiprocessing
import time
def work():
for i in range(10):
print("work:", i)
time.sleep(0.2)
if __name__ == '__main__':
work_process = multiprocessing.Process(target=work)
# 方式 1: 设置子进程为守护进程 (推荐)
work_process.daemon = True
work_process.start()
time.sleep(1)
# 方式 2: 强制关闭子进程 (可能导致资源未释放)
# work_process.terminate()
print("主进程结束")

线程(Thread)
基本概念
线程是 CPU 调度的基本单位。一个进程可以包含多个线程,线程依附于进程存在。相比进程,线程的创建和切换开销更小。
创建线程的步骤
使用 threading 模块。
- 导入模块:
import threading - 创建线程对象:
threading.Thread(target=..., args=..., kwargs=...) - 启动线程:
thread_obj.start()
代码示例:多线程多任务
同样用'写代码 + 听音乐'的例子对比多线程的效果。
""" 多线程的使用 """
import threading
import time
def coding():
for i in range(3):
print("I'm coding")
time.sleep(0.2)
def music():
for i in range(3):
print("I'm music...")
time.sleep(0.2)
if __name__ == '__main__':
coding_thread = threading.Thread(target=coding)
music_thread = threading.Thread(target=music)
coding_thread.start()
music_thread.start()

线程的参数传递
与进程类似,线程也支持 args 和 kwargs 传参。
""" 线程带参数的任务 """
import threading
import time
def coding(name, num):
for i in range(num):
print(f"{name}正在编写第{i}行代码")
time.sleep(0.2)
def music(name, num):
for i in range(num):
print(f"{name}正在听第{i}首音乐")
time.sleep(0.2)
if __name__ == '__main__':
coding_thread = threading.Thread(target=coding, args=("小王", 3))
music_thread = threading.Thread(target=music, kwargs={"name": "大大大", "num": 6})
coding_thread.start()
music_thread.start()
线程的执行特性
-
无序性:线程的执行顺序由操作系统调度决定,具有随机性。CPU 采用时间片轮转或抢占式调度,哪个线程抢到时间片就执行哪个。
-
共享全局变量:同一进程内的线程共享全局变量。这虽然提高了通信效率,但也带来了数据竞争问题。
""" 线程之间共享全局变量 """
my_list = []
def write_data():
for i in range(3):
my_list.append(i)
print("add:", i)
print("write_data:", my_list)
def read_data():
print("read_data:", my_list)
if __name__ == '__main__':
t1 = threading.Thread(target=write_data)
t2 = threading.Thread(target=read_data)
t1.start()
time.sleep(1)
t2.start()

线程安全问题与锁
当多个线程同时修改同一个全局变量时,可能会出现数据不一致。例如两个线程同时对计数器加 1,理论上结果应为 2,但实际可能只加了 1。
原因分析:
- 线程 t1 读取值 0。
- 系统调度切换到 t2,t2 也读取到 0。
- t1 计算后写回 1。
- t2 计算后写回 1。
- 最终结果为 1,丢失了一次更新。
解决方案:互斥锁(Lock) 通过锁机制,保证同一时刻只有一个线程能操作共享数据。
- 流程:创建锁 -> 上锁 (
acquire) -> 执行代码 -> 释放锁 (release)。 - 死锁风险:如果忘记释放锁,或者嵌套锁处理不当,会导致程序卡死。
""" 线程之间共享全局变量可能会出现安全问题 """
import threading
my_count = 0
# 创建锁
lock = threading.Lock()
def write_data1():
global my_count
lock.acquire() # 获取锁
for i in range(1000000):
my_count += 1
lock.release() # 释放锁
def write_data2():
global my_count
lock.acquire() # 获取锁
for i in range(1000000):
my_count += 1
lock.release() # 释放锁
if __name__ == '__main__':
t1 = threading.Thread(target=write_data1)
t2 = threading.Thread(target=write_data2)
t1.start()
t2.start()

守护线程
与进程类似,线程也可以设置为守护模式。主线程退出时,守护线程会自动终止。
""" 设置守护线程 """
import threading
import time
def work():
for i in range(10):
print("working")
time.sleep(0.2)
if __name__ == '__main__':
# 方式 1: 创建时设置 daemon=True
t = threading.Thread(target=work, daemon=True)
# 方式 2: 创建后设置
# t.setDaemon(True)
t.start()
time.sleep(1)
print("主进程结束")

进程与线程对比
关系
- 线程依附于进程,没有进程就没有线程。
- 一个进程默认至少有一条线程,但可以创建多条。

核心区别
| 特性 | 进程 | 线程 |
|---|---|---|
| 资源分配 | 操作系统资源分配的基本单位 | CPU 调度的基本单位 |
| 内存空间 | 进程间不共享全局变量(隔离) | 线程间共享全局变量 |
| 开销 | 创建和切换开销大 | 创建和切换开销小 |
| 稳定性 | Python 中多进程稳定性更强 | 易受 GIL 限制,需注意同步 |

优缺点总结
- 进程:优点是可以利用多核 CPU 并行计算;缺点是资源消耗大,通信复杂。
- 线程:优点是轻量级,通信方便;缺点是不能真正利用多核(受 GIL 影响),且存在数据竞争风险。


