多任务之进程
一 多任务的概念
指在同一时间内同时执行多个任务. 例如 可以同时运行微信和QQ
二 多任务执行方式
1并发:指单核CPU 执行任务 . 操作系统让各个软件交替执行. 由于CPU执行速度太快,让我们感觉是在同时运行的.
2并行:指多核CPU执行任务,操作系统会给每个内核安排一个软件进行执行.是真正一起执行软件的.
三 多进程方式完成多任务
1进程的概念 : 一个正在运行的程序或者软件就是一个进程.进程是操作系统分配资源的基本单位. 即每启动一个进程,操作系统就会给其分配一定的运行资源(内存资源)保证进程的运行。
2创建两个子继承,并将各自任务交给他们,启动进程,就能执行多任务.主进程: 直接执行的进程. 子进程:手动创建的进程叫子进程
3 多进程执行多任务流程:
- 3.1 导入multiprocessing进程包和time模块(time模块作用是延迟时间,观察代码执行状态)
- 3.2 创建任务(指函数或者类),最好是循环任务,使我们可以更加清晰的看出代码执行状态
- 3.3 加入 if __name__ == '__main__': 判断代码
- 3.4 将任务交给子进程处理
- 3.5 创建子进程 变量名 = multiprocessing.Process(group=None,target =任务名,name,args ,kwargs )
- group 指定进程组 目前只能使用None
- target = 任务名 name = 进程名字(默认为Process-1-N)
- args 元组数据传参
- kwargs 字典数据进行传参
- 3.6 启动子程序 变量名.strat()
4 进程执行是无序的,具体哪个先执行是由CPU调度决定的
5 结论:
通过观察,我们发现两个任务可以同时进行了,这样就使用进程方式完成了多任务执行.
四.获取多任务编号
1.获取进程编号作用: 验证子进程和主进程的关系.得知子进程是由哪个主进程创建的.
2.获取进程编号的两种操作方法:
- os.getpid() 获取当前进程编号
- os.getppid() 获取当前进程父进程编号
- multiprocessing.current_process() 获取当前进程对象
3.代码执行流程如下:
- 3.1在多进程执行多任务的基础上书写代码
- 3.2导入 os模块
- 3.3获取主进程(即直接执行的进程)进程编号和进程名称,并格式化输出print('mian_id',os.pid,multiprocessing.current_process() )
- 3.4 获取子进程(在任务中)进程编号,进程名称print('sing_id',os.pid,multiprocessing.current_process() )
- 3.5 获取子进程的父进程编号 print('sing_parent_id',os.ppid)
- 3.6 观察父进程编号和主进程编号
- 3.7 os.kill(进程编号,9)根据进程编号强制杀死该进程.
4.结论:
子进程的父进程编号和主进程编号相同,证明子进程是由主进程创建的.
五.进程执行带参数的多任务
1 创建任务带有参数,就要在进程处传入实参
2 args = () 第一种传参方法,元组传参,args是一个位置参数
3 kwargs ={} 第二章传参方法,字典传参,字典的key值要和函数的参数名保持一致,是关键字参数
4 args 和 kwargs 一起执行
sub_process = multiprocessing.Process(target=info_show,args = ('张三',), kwargs={'age' : 20 })
六 进程的注意点
1 进程之间不共享数据
①代码验证流程如下:
- 分析: 设置两个进程,一个添加数据,另一个读取数据,观察结果
- 1.1 导入multiprocessing 包 和time模块
- 1.2 设置全局变量,一个列表
- 1.3 创建第一个任务:向列表中添加数据(格式化输出每次打印的数据)
- 1.4 创建第二个任务:读取列表
- 1.5 创建两个子进程,分别将两个任务交给进程
- 1.6 启动进程
- 1.7 为了效果明显,可使用join()函数 等待添加函数结束后再执行读取函数
- 结论: 可以看出两个子进程之间是不共享全局变量的
②原理分析:
- 2.1 主进程创建子进程的过程,实质是子进程拷贝主进程的数据资源,相当于副本
所以两个进程是互不干涉的,其中一个操作全局变量也就和另一个进程没有关系.
③ Windows系统会无限递归创建子进程.解决办法: 通过判断是否是主模块
if __name__ == '__main__':
作用1: 防止别人导入文件的时候执行里面的代码
作用2: 防止Windows系统中递归创建子进程.
2 主进程要等待子进程执行结束再结束
①代码执行流程如下:
- 2.1 导入multiprocessing包和time模块
- 2.2 创建任务,
- 2.3 将任务交给子进程
- 2.4 主进程启动
- 2.5 控制时间 子进程的完成时间要多于主进程时间
- 2.6 观察结果
- 结论: 主进程会等待子进程结束再结束
② 解决办法:
1. 设置为子进程守护主进程,主进程结束后,子进程会被销毁(在start之前设置)
sub_process.daemon = True
2. 销毁子进程
sub_process.terminate()
多任务之线程
一.线程的介绍
1.概念:线程是进程中执行代码的一个分支,每个分支(线程)想要执行代码需要CPU进行调度.线程是CPU调度的基本单位. 每个进程至少有一个线程,就是主线程.
2.多线程完成多任务
二.多线程执行多任务
①代码执行流程如下:
- 2.1 导入threading线程模块和time模块
- 2.2 创建任务,查看当前线程对象 threading.current_thread()
- 2.3 if __name__ == '__main__': 判断
- 2.4 创建子线程,将任务交给子线程执行 threading.Thread()(参数与进程相同)
- 2.5 启动线程
②多线程传入参数 args : 元组传参. kwargs: 字典传参
三.线程注意点
3.1 线程执行是无序的
代码执行流程如下:
- 导入线程模块
- 创建大量子线程 for循环创建
- 创建任务,延时1S
- 启动子线程
结论:
线程的运行是无序的,该结果也同样适用于进程.
3.2 主线程会等待子线程执行结束再结束
代码执行流程如下:
- 导入线程模块
- 创建死循环任务,延时0.3S
- 创建子线程
- 启动子线程,延时1S 结束后打印over
结论:
线程的运行中,主进程要等待子线程结束再结束.
解决办法:
- 设置子线程守护主线程
sub_thread = threading.Thread(target=task,daemon=True)
sub_thread.setDaemon(True)
3.3 线程之间共享全局变量
需求: 创建两个任务,交给线程执行,一个任务全局变量执行添加数据,一个执行读取数据
代码执行流程如下:
- 导入线程模块
- 定义全局变量,创建两个任务,一个添加数据,一个执行读取数据
- 创建子线程
- 启动子线程,设置延迟观察效果
结论:
线程之间共享全局变量
原理: 不同线程之间的操作是在同一个进程内执行的.所以会共享全局变量
3.4 线程之间共享全局变量出现的错误问题
需求:创建两个任务,交给线程执行,分别执行从一百万个1相加,观察结果
代码执行流程如下:
- 导入线程模块
- 定义全局变量
- 创建子线程
- 启动子线程,观察结果
结论:
线程之间共享大量全局变量出现错误.
原理:
两个线程之间同步执行一个全局变量,出现重复错误
解决方法:
线程同步: 保证同一时刻只能有一个线程去操作全局变量 同步: 就是协同步调,按预定的先后次序进行运行。
- join等待 一个线程全部执行后再执行
- 互斥锁
四.互斥锁和死锁
4.1 互斥锁
1概念:对共享数据进行锁定,保证同一时刻只能有一个线程去操作.
注意点: 互斥锁是多个线程一起去抢,抢到的先执行,没抢到的只能等执行完毕,释放锁之后后再去抢.
2 使用方法:
锁是线程模块的一个变量,通过调用这个函数可以创建锁
# 创建锁 mutex = threading.Lock()
# 上锁 mutex.acquire() ...这里编写代码能保证同一时刻只能有一个线程去操作, 对共享数据进行锁定... # 释放锁 mutex.release()
3 注意点:
acquire(上锁)和release(释放)方法之间的代码同一时刻只能有一个线程去操作
如果在调用acquire(上锁)方法的时候 其他线程已经使用了这个互斥锁,那么此时acquire(上锁)方法会堵塞,直到这个互斥锁释放后才能再次上锁。
acquire 和release 输出时候没有提示.手动输入
4 互斥锁完成2个线程对同一个全局变量各加100万次的代码操作
代码执行流程如下:
- 导入线程模块
- 定义全局变量
- 创建锁
lock = threading.Lock()
- 创建任务
- 任务上锁,任务解锁
lock.acquire() lock.release()
- 创建子线程
- 启动子线程,观察结果
详解:
设置了互斥锁,哪个线程抢到数据我们不管,当一个线程抢到了数据,他就会执行完成,再释放.第二个任务再抢到去执行.这样我们就可以解决问题了
注意:
使用互斥锁,由多任务转变成了单任务执行,会降低执行效率
多个线程要同时使用互斥锁
互斥锁使用不好会变成死锁
4.2 死锁
1 概念: 一直等待对方释放锁的情景就是死锁
2 造成的后果:会造成应用程序的停止响应,不能再处理其它任务了。
3 死锁示例:
4 死锁产生,程序就会停止响应,应用程序就无法继续向下执行了
五.进程和线程的区别
1 关系对比:
- 线程是依附在进程的,没有进程就没有线程
- 一条进程默认一条线程,可以创建多条线程
2 区别对比
- 进程之间不共享全局变量
- 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步
- 创建进程的资源开销要比创建线程的资源开销要大
- 进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位
- 线程不能够独立执行,必须依存在进程中
- 多进程开发比单进程多线程开发稳定性要强
3 优缺点对比
- 进程优缺点:
-
- 优点:可以用多核
- 缺点:资源开销大
- 线程优缺点:
-
- 优点:资源开销小
- 缺点:不能使用多核