【多任务编程】进程
前言
本篇文章将详细介绍多任务编程,进程,多进程的使用,获取进程编号,进程执行带有参数的任务,进程的注意点。接下来我们开始吧。
一、多任务的介绍
- 问:如何让两个函数或者方法同时执行?
- 答:要想实现这种操作就需要使用多任务。
多任务的好处是充分利用CPU资源,提高程序执行效率
多任务是指在同一时间内执行多个任务,例如: 现在电脑安装的操作系统都是多任务操作系统,可以同时运行着多个软件
- 多任务的执行方式:
- 并发
并行
并发:
在一段时间内交替去执行任务。
例如:
对于单核cpu处理多任务,操作系统轮流让各个软件交替执行,假如:软件1执行0.01秒,切换到软件2,软件2执行0.01秒,再切换到软件3,执行0.01秒……这样反复执行下去。表面上看,每个软件都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像这些软件都在同时执行一样,这里需要注意单核cpu是并发的执行多任务的。
并行:
对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的软件,多个内核是真正的一起执行软件。这里需要注意多核cpu是并行的执行多任务,始终有多个软件一起执行。
总结:
使用多任务就能充分利用CPU资源,提高程序的执行效率,让你的程序具备处理多个任务的能力。
多任务执行方式有两种方式:并发和并行,这里并行才是多个任务真正意义一起执行。
二、进程
1.进程的介绍
在Python程序中,想要实现多任务可以使用进程来完成,进程是实现多任务的一种方式。
2.进程的概念
- 一个正在运行的程序或者软件就是一个进程,它是操作系统进行资源分配的基本单位,也就是说每启动一个进程,操作系统都会给其分配一定的运行资源(内存资源)保证进程的运行。
比如:现实生活中的公司可以理解成是一个进程,公司提供办公资源(电脑、办公桌椅等),真正干活的是员工,员工可以理解成线程。
3.单进程和多进程
多进程可以完成多任务,每个进程就好比一家独立的公司,每个公司都各自在运营,每个进程也各自在运行,执行各自的任务。
三、多进程的使用
#导入进程包
import multiprocessing
Process进程类的说明
Process([group [, target [, name [, args [, kwargs]]]]])
group:指定进程组,目前只能使用None
target:执行的目标任务名
name:进程名字
args:以元组方式给执行任务传参
kwargs:以字典方式给执行任务传参
Process创建的实例对象的常用方法:
start():启动子进程实例(创建子进程)
join():等待子进程执行结束
terminate():不管任务是否完成,立即终止子进程
"""
1. 导入进程包
2. 创建子进程(自己手动创建的进程称为子进程, 在__init__.py文件中已经导入了Process类)
3. 启动进程执行的任务
"""
# 1. 导入进程包
import multiprocessing
import time
def dance():
for i in range(3):
print("跳舞中...")
time.sleep(0.2)
def sing():
for i in range(3):
print('唱歌中...')
time.sleep(0.2)
# 2. 创建子进程(自己手动创建的进程称为子进程, 在__init__.py文件中已经导入了Process类)
# 1.group: 进程组,目前只能使用None,一般不需要设置
# 2.target: 进程执行的目标任务
# 3.name: 进程名,如果不设置,默认是Process-1,....
if __name__ == '__main__':
dance_process = multiprocessing.Process(target=dance)
sing_process = multiprocessing.Process(target=sing)
# 3. 启动进程执行对应的任务
dance_process.start()
sing_process.start()
# 进程执行是无序的,具体那个进程先执行由操作系统调度决定
四、获取进程编号
- 获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的。
os.getpid() 表示获取当前进程编号
os.getppid() 表示获取当前父进程编号
# 1.导入进程包
import multiprocessing
import time
import os
def dance():
# 获取当前进程(子进程)的编号
dance_process_id = os.getpid()
# 获取当前进程对象,查看当前代码是由哪个进程执行的: multiprocessing.current_process()
print('dance_process_id:', dance_process_id, multiprocessing.current_process())
# 获取当前进程的父进程编号
dance_process_parent_id = os.getppid()
print('dance_process的父进程编号是:', dance_process_parent_id)
for i in range(3):
print("跳舞中...")
time.sleep(0.2)
# 扩展: 根据进程编号强制杀死指定进程
os.kill(dance_process_id, 9)
def sing():
# 获取当前进程(子进程)的编号
sing_process_id = os.getpid()
# 获取当前进程对象,查看当前代码是由那个进程执行的 : multiprocessing.current_process()
print("sing_process_id:", sing_process_id, multiprocessing.current_process())
# 获取当前进程的父进程编号
sing_process_parent_id = os.getppid()
print("sing_process的父进程编号是:", sing_process_parent_id)
for i in range(3):
print("唱歌中...")
time.sleep(0.2)
if __name__ == '__main__':
# 获取当前进程(主进程)的编号
main_process_id = os.getpid()
# 获取当前进程对象,查看当前代码是由哪个进程执行的: multiprocessing.current_process()
print('main_process_id:', main_process_id, multiprocessing.current_process())
# 2. 创建子进程(自己手动创建的进程称为子进程, 在__init__.py文件中已经导入的Process类)
# 1. group: 进程组,目前只能使用None,一般不需要设置
# 2. target: 进程执行的目标任务
# 3. name: 进程名,如果不设置,默认是Process-1, ......
dance_process = multiprocessing.Process(target=dance, name='dance_process')
print('dance_process:', dance_process)
sing_process = multiprocessing.Process(target=sing, name='sing_process')
print('sing_process:', sing_process)
# 3. 启动进程执行对应的任务
dance_process.start()
sing_process.start()
# 进程执行是无序的,具体那个进程先执行是由操作系统调度决定
总结:
获取当前进程编号
os.getpid()
获取当前父进程编号
os.getppid()
获取进程编号可以查看父子进程的关系
五、进程执行带有参数的任务
前面我们使用进程执行的任务是没有参数的,假如我们使用进程执行的任务带有参数,如何给函数传参呢?
Process类执行任务并给任务传参数有两种方式:
args 表示以元组的方式给执行任务传参
kwargs 表示以字典方式给执行任务传参
import multiprocessing
# 显示信息的任务
def show_info(name, age):
print(name, age)
if __name__ == '__main__':
# 创建子进程
# 以元组方式传参, 元组里面的元素要和函数参数顺序一致
# sub_process = multiprocessing.Process(target=show_info, args=('王五', 18))
# # 启动进程
# sub_process.start()
# 以字典方式传参,字典里面的key要和函数里面的参数名保持一致,没有顺序要求
# sub_process = multiprocessing.Process(target=show_info, kwargs={'age': 20, 'name': '李四'})
# # 启动进程
# sub_process.start()
sub_process = multiprocessing.Process(target=show_info, args=('张三',), kwargs={'age': 19})
# 启动进程
sub_process.start()
进程执行任务并传参有两种方式:
元组方式传参(args): 元组方式传参一定要和参数的顺序保持一致。
字典方式传参(kwargs): 字典方式传参字典中的key一定要和参数名保持一致。
六、进程注意点
- 1.进程之间不共享全局变量
import multiprocessing
import time
# 定义全局变量列表
g_list = list() # => []
# 添加数据的任务
def add_data():
for i in range(5):
# 因为列表是可变类型,可以在原有内存的基础上修改数据,并且修改后内存地址不变
# 所以不需要加上global关键字
# 加上global 表示声明要修改全局变量的内存地址
g_list.append(i)
print('add:', i)
time.sleep(0.2)
print('添加完成:', g_list)
def read_data():
print('read:', g_list)
if __name__ == '__main__':
# 提示: 对应linux和mac主进程执行的代码不会进程拷贝,但是对应window系统来说主进程执行的代码也会进行拷贝执行
# 对应window来说创建子进程的代码如果进程拷贝执行相当于递归无限制进行创建子进程,会报错
# 如果解决windows递归创建子进程,通过判断是否是主模块来解决
# 理解说明: 直接执行的模块就是主模块,那么直接执行的模块里面就应该添加判断是否是主模块的代码
# 1. 防止别人导入文件的时候执行main里面的代码
# 2. 防止windows系统递归创建子进程
# if __name__ == '__main__':
# 添加数据的子进程
add_process = multiprocessing.Process(target=add_data)
# 读取数据的子进程
read_process = multiprocessing.Process(target=read_data)
# 启动进程执行对应的任务
add_process.start()
add_process.join()
print("main:", g_list)
read_process.start()
# 结论: 进程之间不共享全局变量
进程之间不共享全局变量总结:
创建子进程会对主进程资源进行拷贝,也就是说子进程是主进程的一个副本,之所以进程之间不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已。
- 2.主进程会等待所有的子进程执行结束再结束
我们可以设置守护主进程 或者 在主进程退出之前 让子进程销毁
守护主进程:
守护主进程就是主进程退出子进程销毁不再执行
子进程销毁:
子进程执行结束
"""
# 解决办法: 主进程退出子进程销毁
# 1. 让子进程设置成为守护主进程,主进程退出子进程销毁,子进程会依赖主进程
# 2. 让主进程退出之前先让子进程销毁
"""
import multiprocessing
import time
def task():
while True:
print('任务执行中...')
time.sleep(0.3)
# 判断是否是直接执行的模块, 程序入口模块
# 标准python写法,直接执行的模块,需要加上判断是否是主模块的代码
if __name__ == '__main__':
# 创建子进程
sub_process = multiprocessing.Process(target=task)
# 把子进程设置成为守护主进程,以后主进程退出子进程直接销毁
# sub_process.daemon = True
sub_process.start()
# 主进程延时0.5秒钟
time.sleep(0.5)
# 退出主进程前,先让子进程进行销毁
sub_process.terminate()
print('over')
# 结论: 主进程会等待子进程执行完成以后程序再退出
主进程会等待所有的子进程执行结束再结束总结
为了保证子进程能够正常的运行,主进程会等所有的子进程执行完成以后再销毁,设置守护主进程的目的是主进程退出子进程销毁,不让主进程再等待子进程去执行。
设置守护主进程方式: 子进程对象.daemon = True
销毁子进程方式: 子进程对象.terminate()
总结
到这里这篇文章的内容就结束了,谢谢大家的观看,如果有好的建议可以留言喔,谢谢大家啦