别再傻傻分不清!Python进程和线程入门到实操一看就会
前言
进程是操作系统资源分配的基本单位,拥有独立的内存空间,稳定性高但开销大;线程是CPU调度的基本单位,共享所属进程的内存资源,轻量但安全性低。两者没有绝对的优劣,只有是否适合当前场景。
Python进程
多任务的介绍
多个任务同时执行能够充分利用CPU资源,大大提高程序执行效率
多任务的概念
概念:多任务是指在同一时间内执行多个任务(给我们的感觉)。
例如:现在电脑安装的操作系统都是多任务操作系统,可以同时运行着多个软件。
多任务的两种表现形式
- 并发:在一段时间内,交替执行任务
- 并行:在一段时间内,真正的同时一起执行多个任务
并发
在一段时间内交替去执行多个任务
对于单核cpu处理多任务,操作系统轮流让各个任务交替执行,假如:软件1执行0.01秒,切换到软件2,软件2执行0.01秒,再切换到软件3,执行0.01秒……这样反复执行下去 , 实际上每个软件都是交替执行的 . 但是,由于CPU的执行速度实在是太快了,表面上我们感觉就像这些软件都在同时执行一样 . 这里需要注意单核cpu是并发的执行多任务的
并行
在一段时间内真正的同时一起执行多个任务
对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的任务,多个内核是真正的一起同时执行多个任务。这里需要注意多核cpu是并行的执行多任务,始终有多个任务一起执行。
进程的概念
进程(Process)是CPU资源分配的最小单位,它是操作系统进行资源分配和调度运行的基本单位
通俗理解:一个正在运行的程序就是一个进程 . 例如:正在运行的qq , 微信等他们都是一个进程
注意:一个程序运行后至少有一个进程


Python中多进程的基本工作方式
程序运行起来形成主进程;在主进程上创建子进程
多进程完成多任务
进程的创建步骤
- 导入进程工具包
import multiprocessing
- 通过进程类 实例化进程 对象
子进程对象 = multiprocessing.Process()
- 启动进程执行任务
进程对象.start()
通过进程类实例化创建进程对象
子进程对象 =
multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={})
- group—参数未使用,值始终为None
- target—表示调用对象,即子进程要执行的任务(回调函数入口地址)
- args—表示以元组的形式向子任务函数传参,元组方式传参一定要和参数的顺序保持一致
- kwargs—表示以字典的方式给子任务函数传参,字典方式传参字典中的key要和参数名保持一致
- name—为子进程的名称
使用多进程来模拟一边编写代码,一边听音乐功能实现
import multiprocessing
import time
#编写代码任务
def coding():
for i in range(3):
print(f"第{i+1}次coding......")
time.sleep(0.2)
# 听音乐任务
def music():
for i in range(3):
print(f"第{i+1}次music...")
time.sleep(0.2)
if __name__ == '__main__':
# 2.通过进程类创建进程对象,关联doding函数
coding_process = multiprocessing.Process(target=coding)
# 2.通过进程类创建进程对象,关联music函数
music_process = multiprocessing.Process(target=music)
coding_process.start()
music_process.start()

import multiprocessing
import time
#编写代码任务
def coding(num, name):
for i in range(1,num+1):
time.sleep(0.1)
print(f"{name}在编写第{num}次coding......")
# 听音乐任务
def music(count,name):
for i in range(1,count+1):
time.sleep(0.1)
print(f"{name}在听第{count}次music...")
if __name__ == '__main__':
# 2.通过进程类创建进程对象,关联doding函数
coding_process = multiprocessing.Process(target=coding, args=(4,'hgq'))
# 2.通过进程类创建进程对象,关联music函数
music_process = multiprocessing.Process(target=music,kwargs={'name': 'ming','count': 5})
coding_process.start()
music_process.start()

元组方式传参 :元组方式传参一定要和任务函数的参数顺序保持一致。
字典方式传参:字典方式传参字典中的key一定要和任务函数的参数保持一致
获取进程编号
进程编号的作用
进程编号唯一标识一个进程, 方便管理进程。
在一个操作系统中,一个进程拥有的进程号是唯一的,进程号可以反复使用。
获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的
获取进程编号的两种操作
- 获取当前进程编号
- 获取当前父进程编号
os.getpid()的使用
import os
print(f"当前进程编号:{os.getpid()}")

import multiprocessing
pid = multiprocessing.current_process().pid
print(f"当前进程号为:{pid}")

os.getppid()的使用
import os
pid = os.getppid()
print(f"当前进程的父进程的id为:{pid}")

进程的注意点
1.进程之间不共享全局变量
2. 主进程会等待所有的子进程执行结束再结束
进程间不共享全局变量
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__':
# 创建写入数据进程
write_process = multiprocessing.Process(target=write_data)
# 创建读取数据进程
read_process = multiprocessing.Process(target=read_data)
# 启动进程执行相应任务
write_process.start()
time.sleep(1)
read_process.start()

默认主进程会等待所有的子进程执行结束再结束
假如我们现在创建一个子进程,子进程执行完大概需要2秒钟,现在让主进程执行1秒钟就退出程序:
import time
import multiprocessing
def work():
for i in range(10):
print("工作中...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子进程
workProcess = multiprocessing.Process(target=work)
# 启动子进程
workProcess.start()
# 延时1秒
time.sleep(1)
print("主进程执行完毕")

不让主进程等待子进程
方法1:子进程设置守候进程
子进程设置守护主进程的目的:是主进程退出子进程销毁,不让主进程再等待子进程去执行。
import time
import multiprocessing
def work():
for i in range(10):
print("工作中...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子进程
workProcess = multiprocessing.Process(target=work)
#设置主进程守护
workProcess.daemon = True
# 启动子进程
workProcess.start()
# 延时1秒
time.sleep(1)
print("主进程执行完毕")

方法2:子进程自己主动的终止子进程
import time
import multiprocessing
def work():
for i in range(10):
print("工作中...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子进程
workProcess = multiprocessing.Process(target=work)
# 启动子进程
workProcess.start()
# 延时1秒
time.sleep(1)
# 手动结束子进程
workProcess.terminate() #这种不建议使用,僵尸进程,不会清理资源
print("主进程执行完毕")

为了保证子进程能够正常的运行,主进程会等所有的子进程执行完成以后再销毁,设置守护主进程的目的是主进程退出子进程销毁,不让主进程再等待子进程去执行。
设置守护主进程方式: 子进程对象.daemon = True
销毁子进程方式: 子进程对象.terminate()
Python线程
线程的介绍
在Python中,想要实现多任务除了使用进程,还可以使用线程来完成。
线程的概念
进程是分配资源的基本单位, 一旦创建一个进程就会分配一定的资源;
线程是cpu调度的基本单位,每个进程至少都有一个线程,而这个线程就是我们通常说的主线程。

线程用来实现多任务编程
多线程完成多任务
线程创建的步骤
- 导入线程模块
import threading
- 通过线程类创建线程对象
线程对象 = threading.Thread(target=任务名)
- 启动线程执行任务
线程对象.start()
线程类参数说明
线程对象=threading.Thread([group [, target [, name [, args [, kwargs]]]]])
group: 线程组,目前只能使用None
target: 执行的目标任务名
args: 以元组的方式给执行任务传参,元组方式传参一定要和目标任务函数参数的顺序保持一致。
kwargs: 以字典方式给执行任务传参,字典方式传参字典中的key一定要和参数的顺序保持一致
name: 线程名,一般不用设置
多线程完成多任务的代码
import time
import threading
#编写代码任务
def coding():
for i in range(3):
print("coding...")
time.sleep(0.2)
#听音乐任务
def music():
for i in range(3):
print("music...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子线程
codingThread = threading.Thread(target=coding)
musicThread = threading.Thread(target=music)
# 启动子线程启动任务
codingThread.start()
musicThread.start()

线程带参数的任务
import time
import threading
#编写代码任务
def coding(num, name):
for i in range(num):
print(f"{name}正在编写第{i+1}行代码...")
time.sleep(0.2)
#听音乐任务
def music(count, name):
for i in range(count):
print(f"{name}正在听第{i+1}首歌...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子线程
codingThread = threading.Thread(target=coding,args=(4,'小明'))
musicThread = threading.Thread(target=music,kwargs={'count': 5, 'name': '小梁'})
# 启动子线程启动任务
codingThread.start()
musicThread.start()

线程的注意点
- 线程之间执行是无序的
- 主线程会等待所有的子线程执行结束再结束
- 线程之间共享全局变量
- 线程之间共享全局变量数据出现错误问题
线程之间执行是无序的
线程之间执行是无序的,它是由cpu调度决定的 ,cpu调度哪个线程,哪个线程就执行,没有调度的线程是不能执行的。
主线程会等待所有的子线程执行结束再结束
import time
import threading
#编写代码任务
def coding(num, name):
for i in range(num):
print(f"{name}正在编写第{i+1}行代码...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子线程
codingThread = threading.Thread(target=coding,args=(10,'小明'))
# 启动子线程启动任务
codingThread.start()
time.sleep(1)
print("主线程执行完毕")

主线程会等待所有的子线程执行结束再结束
子线程就销毁不再执行:
守护主线程:
守护主线程就是主线程退出子线程销毁不再执行
设置守护主线程有两种方式:
threading.Thread(target=coding, daemon=True)
线程对象.setDaemon(True)
import time
import threading
#编写代码任务
def coding(num, name):
for i in range(num):
print(f"{name}正在编写第{i+1}行代码...")
time.sleep(0.2)
if __name__ == '__main__':
# 创建子线程
codingThread = threading.Thread(target=coding,args=(10,'小明'),daemon=True)
# 启动子线程启动任务
codingThread.start()
time.sleep(1)
print("主线程执行完毕")

线程之间共享全局变量
import threading
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__':
# 创建写入数据线程
write_process = threading.Thread(target=write_data)
# 创建读取数据线程
read_process = threading.Thread(target=read_data)
# 启动进程执行相应任务
write_process.start()
time.sleep(1)
read_process.start()

线程之间共享全局变量可能会导致数据出现错误问题
全局变量数据错误的解决办法:
线程同步: 保证同一时刻只能有一个线程去操作全局变量
同步: 就是协同步调,按预定的先后次序进行运行。如:你说完,我再说, 好比现实生活中的对讲机
线程同步的方式: 锁
互斥锁的使用
互斥锁: 对共享数据进行锁定,保证同一时刻只有一个线程去操作。
互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程进行等待,等锁使用完释放后,其它等待的线程再去抢这个锁。
互斥锁的使用教程
- 互斥锁的创建
mutex = threading.Lock()
- 上锁
mutex.acquire()
- 释放锁
mutex.release()
使用互斥锁保证线程间的数据安全
import threading
# 全局变量
share = 0
# 创建互斥锁
mutex = threading.Lock()
def add_one():
mutex.acquire() # 上锁
for i in range(1000000):
global share
share += 1
mutex.release() # 解锁
print(f"add_one的函数结果为:{share}")
def add_two():
mutex.acquire() # 上锁
for i in range(1000000):
global share
share += 1
mutex.release() # 解锁
print(f"add_two的函数结果为:{share}")
if __name__ == '__main__':
testOne = threading.Thread(target=add_one)
testTwo = threading.Thread(target=add_two)
#启动线程
testOne.start()
testTwo.start()

死锁
一直等待对方释放锁的情景就是死锁。
死锁的原因:
- 使用互斥锁的时候需要注意死锁的问题,
- 未在合适的地方注意释放锁
死锁的结果:
- 会造成应用程序的停止响应,
- 应用程序无法再继续往下执行了

进程和线程的对比
线程是依附在进程里面的,没有进程就没有线程
一个进程默认提供一条线程,进程可以创建多个线程
- 进程之间不共享全局变量
- 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁
- 创建进程的资源开销要比创建线程的资源开销要大
- 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
- 线程不能够独立执行,必须依存在进程中
- Python中多进程开发比单进程多线程开发稳定性要强
进程优缺点:
- 优点:可以用多核
- 缺点:资源开销大
线程优缺点:
- 优点:资源开销小
- 缺点:不能使用多核
总结
进程和线程是Python进阶路上非常实用的技能点,掌握之后你写的脚本效率能直接提升数倍,哪怕只是用来优化自己的日常小工具,也能省下大量的时间。如果你在实操中遇到问题,或者有具体的并发场景想探讨,欢迎在评论区留言交流~
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)