跳到主要内容 Python 多任务编程:进程与线程详解 | 极客日志
Python
Python 多任务编程:进程与线程详解 Python 中的多任务编程概念,包括并发与并行。详细讲解了进程(Process)和线程(Thread)的定义、创建步骤及区别。重点阐述了进程间数据隔离特性,以及线程间共享全局变量时的资源竞争问题,并提供了使用互斥锁(Lock)解决同步问题的方案。通过代码示例对比了多进程与多线程在资源开销、稳定性及核利用上的差异。
星辰大海 发布于 2026/3/22 更新于 2026/4/18 3 浏览介绍
多任务的优势
多个任务同时执行能够充分利用 CPU 资源,大大提高程序执行效率。
之前编写的程序通常是单任务的,即一个函数或方法执行完成后,另一个才能执行。要实现多个任务同时执行,需要使用多任务技术。
概念
多任务是指在同一时间内执行多个任务(给人的感觉)。
例如:现代操作系统都是多任务操作系统,可以同时运行多个软件。
多任务的两种表现形式
并发 :在一段时间内,交替执行任务。
并行 :在一段时间内,真正的同时一起执行多个任务。
进程
进程的概念
进程 (Process) 是 CPU 资源分配的最小单位,它是操作系统进行资源分配和调度运行的基本单位。
通俗理解:一个正在运行的程序就是一个进程。
例如:正在运行的 QQ、微信等它们都是一个进程。
注意:一个程序运行后至少有一个进程。
多进程的作用
在一个简单的程序中,按照代码的执行顺序,func_a 函数执行完毕后才能执行 func_b 函数。如果可以让 func_a 和 func_b 同时运行,显然执行程序的效率会大大提升。
多进程基本工作方式
导入进程工具包:import multiprocessing
通过进程类实例化进程对象:子进程对象 = multiprocessing.Process(...)
group: 参数未使用,值始终为 None
target: 表示调用对象,即子进程要执行的任务
args: 以元组的形式向子任务函数传参
kwargs: 以字典的方式给子任务函数传参
name: 为子进程的名称
启动进程执行任务:进程对象.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( )
__name__ == :
p1 = multiprocessing.Process(target=coding)
p2 = multiprocessing.Process(target=music)
p1.start()
p2.start()
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 curl 转代码 解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
0.2
if
'__main__'
任务函数有参数 """ 进程带参数的任务 """
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__' :
p1 = multiprocessing.Process(target=coding, args=(3 , "小王" ))
p2 = multiprocessing.Process(target=music, kwargs={"num" : 7 , "name" : "大名" })
p1.start()
p2.start()
元组方式传参:元组方式传参一定要和任务函数的参数顺序保持一致。
字典方式传参:字典方式传参字典中的 key 一定要和任务函数的参数保持一致。
进程编号的作用
在一个操作系统中,一个进程拥有的进程号是唯一的,进程号可以反复使用。
获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由哪个主进程创建出来的。
获取进程编号的两种操作:
获取当前进程编号:os.getpid()
获取当前父进程编号:os.getppid()
进程的注意点介绍 例如,在不同进程中修改列表 my_list 并新增元素,试着在各个进程中观察列表的最终结果。
""" 进程之间数据是相互隔离的。因为子进程相当于是父进程的副本,会将父进程的 main 外资源拷贝一份,即:各是各的。"""
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()
创建子进程会对主进程资源进行拷贝,也就是说子进程是主进程的一个副本。之所以进程之间不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已。
假如我们现在创建一个子进程,子进程执行完大概需要 2 秒钟,现在让主进程执行 1 秒钟就退出程序:
""" 默认情况下,主进程会等待子进程执行结束再结束。"""
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 ("主进程结束" )
通过上面代码的执行结果,我们可以得知:主进程会等待所有的子进程执行结束再结束。
让主进程退出时自动销毁子进程,主进程就不再等待子进程执行了。
""" 不让主进程等待子进程 方式 1: 设置子进程为守护进程 (推荐方式) 会释放资源 方式 2: 强制关闭子进程 可能会导致子进程变成垃圾进程,交由 python 解释器自动回收 """
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.daemon = True
work_process.start()
time.sleep(1 )
print ("主进程结束" )
线程
线程的介绍 图中是一个非常简单的程序,一旦运行 hello.py 这个程序,按照代码的执行顺序,func_a 函数执行完毕后才能执行 func_b 函数。如果可以让 func_a 和 func_b 同时运行,显然执行 hello.py 这个程序的效率会大大提升。
线程的作用 线程是进程中的一个执行单元,是 CPU 调度的最小单位。
线程创建的步骤
导入线程模块:import threading
通过线程类创建线程对象:线程对象 = threading.Thread(group, target, name, kwargs)
group: 线程组,目前只能使用 None
target: 执行的目标任务名
args: 以元组的方式给执行任务传参
kwargs: 以字典方式给执行任务传参
name: 线程名,一般不用设置
启动线程执行任务:线程对象.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()
线程带参数的任务 """ 线程带参数的任务 """
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 算力,抢到之后执行任务(大多数语言使用该策略)。
""" 线程调度的随机性 """
import threading
import time
def get_info ():
time.sleep(0.5 )
thread = threading.current_thread()
print (f"{thread.name} 正在执行任务" )
if __name__ == '__main__' :
for i in range (10 ):
t = threading.Thread(target=get_info)
t.start()
假如创建一个子线程,这个子线程执行完大概需要 2.5 秒钟,现在让主线程执行 1 秒钟就退出程序,查看一下执行结果。
""" 主进程会等待所有子进程结束后再结束 """
import threading
import time
def work ():
for i in range (10 ):
print ("working" )
time.sleep(0.2 )
if __name__ == '__main__' :
t = threading.Thread(target=work)
t.start()
time.sleep(1 )
print ("主进程结束" )
假如我们就让主线程执行 1 秒钟,子线程就销毁不再执行,那怎么办呢?我们可以设置守护主线程。
守护主线程就是主线程退出子线程销毁不再执行。
设置守护主线程有两种方式:
创建子进程时设置该线程为守护线程:t = threading.Thread(target=work, daemon=True)
通过线程对象设置为守护线程:t.setDaemon(True)
""" 主进程会等待所有子进程结束后再结束 """
import threading
import time
def work ():
for i in range (10 ):
print ("working" )
time.sleep(0.2 )
if __name__ == '__main__' :
t = threading.Thread(target=work, daemon=True )
t.start()
time.sleep(1 )
print ("主进程结束" )
定义一个列表类型的全局变量,创建两个子线程分别执行,向全局变量添加数据的任务和向全局变量读取数据的任务,查看线程之间是否共享全局变量数据。
""" 线程之间共享全局变量 """
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()
定义两个函数,实现循环 100 万次,每循环一次给全局变量加 1,创建两个子线程执行对应的两个函数,查看计算后的结果。
my_count = 0
def write_data1 ():
global my_count
for i in range (1000000 ):
my_count += 1
print (f"write_data1:{my_count} " , end="\n" )
def write_data2 ():
global my_count
for i in range (1000000 ):
my_count += 1
print (f"write_data2:{my_count} " )
if __name__ == '__main__' :
t1 = threading.Thread(target=write_data1)
t2 = threading.Thread(target=write_data2)
t1.start()
t2.start()
两个线程对同一个全局变量 my_count 进行加 1 运算,由于是多线程同时操作,两个方法交替执行,有可能出现下面情况:
t1 取得 my_count=0。此时系统把 t1 调度为等待状态,把 t2 转换为 running 状态。
由于 t1 还没有执行完成,t2 拿到的 my_count=0。
t1 执行完毕后 my_count=1,t2 执行完毕后 my_count 还是 1。
相当于 t1 和 t2 都对 my_count 加 1,应该得到 2,实际得到还是 1。
线程同步:保证同一时刻只能有一个线程去操作全局变量。
同步:就是协同步调,按预定的先后次序进行运行,好比现实生活中的对讲机,你说完,我再说。
线程同步的方式 [加锁思想]。
互斥锁:对共享数据进行锁定,保证同一时刻只有一个线程去操作。
互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程进行等待,等锁使用完释放后,其它等待的线程再去抢这个锁。
互斥锁的使用流程:
创建互斥锁:mutex = threading.Lock()
上锁:mutex.acquire()
释放锁:mutex.release()
一直等待对方释放锁的情景就是死锁。死锁的原因是没有在合适的地方注意释放锁。死锁的结果会造成应用程序的停止响应,应用程序无法再继续往下执行了。
""" 线程之间共享全局变量可能会出现安全问题 """
my_count = 0
lock = threading.Lock()
def write_data1 ():
global my_count
lock.acquire()
for i in range (1000000 ):
my_count += 1
print (f"write_data1:{my_count} " , end="\n" )
lock.release()
def write_data2 ():
global my_count
lock.acquire()
for i in range (1000000 ):
my_count += 1
print (f"write_data2:{my_count} " )
lock.release()
if __name__ == '__main__' :
t1 = threading.Thread(target=write_data1)
t2 = threading.Thread(target=write_data2)
t1.start()
t2.start()
对比
关系对比
线程是依附在进程里面的,没有进程就没有线程。
一个进程默认提供一条线程,进程可以创建多个线程。
区别对比
进程之间不共享全局变量。
线程之间共享全局变量,但是要注意资源竞争的问题,解决办法:互斥锁。
创建进程的资源开销要比创建线程的资源开销要大。
进程是操作系统资源分配的基本单位,线程是 CPU 调度的基本单位。
线程不能够独立执行,必须依存在进程中。
Python 中多进程开发比单进程多线程开发稳定性要强。
优缺点对比