第十二章 并发编程
Posted zhulipeng-1998
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十二章 并发编程相关的知识,希望对你有一定的参考价值。
第十二章 并发编程
操作系统
- 控制软硬资源
- 调度进度
多道技术
产生背景
针对单核,实现并发
是什么?
内存中存放多道程序
宏观上并行,微观上串行
看起来多个程序同时运行
实际上多道程序轮流使用CPU
时空复用
内存中同时存放多道程序
进程
是什么?
- 操作系统进行分配资源和调度的基本单位
- 线程的容器
- 正在运行的程序过程中的一种抽象
- 本质实在多道程序系统中的一次程序执行过程
特征
- 动态性:动态创建动态消亡
- 并发性:和其他进程并发执行
- 独立性:是一个能独立运行的单位
- 结构特征:数据、程序、进程控制三跨部分组成
调度算法
先来先服务:长作业有利,短作业无利
短作业优先:短作业优先度高,不利于长作业
时间片轮转法:将CPU处理时间切成时间片,进程用完时间片还没执行完,就排到末尾等待下一次执行
多级反馈队列:设置多个队列,赋予优先级
4.1 新进程进入内存,放入第一队列,按照先来先服务调度
4.2 不能在分配的时间片内完成,降入第二队列,按照先来先服务调度
依次降级时,按时间片轮转法运行
进程调度:按照多级反馈队列+时间片轮转调度
并发与并行
并发:看起来像同时运行
并行:真正意义上的同时运行
单核计算机不存在并行,但可以并发
进程的三种状态
- 就绪态:进程分配到除CPU外的所有资源
- 运行态:进程获得处理机,执行完一个时间片,进入就绪态
- 阻塞态:等待时间发生,放弃处理机。(遇IO操作、或使用CPU时间过长)
同步、异步、阻塞、非阻塞
- 同步:两个任务等待提交,第二个必须等待第一个提交完毕才能提交
- 异步:两个任务等待提交,提交第一个的同时就可以直接提交第二个任务,不用等待。
- 阻塞:程序的阻塞态(遇IO操作或CPU执行时间长)
- 非阻塞:程序进入就绪态、运行态
进程的两种创建方式
- 直接使用process实例
- 继承process,重写run方法
进程的使用
from multiprocessing import Process
p=Process(target=func,)
p.start()
p1.join
p.daemon=True
僵尸进程、孤儿进程
僵尸进程:
1.1 子进程死亡,父进程没回收
1.2 任何进程都会变成僵尸进程
孤儿进程:父进程意外死亡
守护进程
- p.daemon=True,在start()之前使用
- 在主进程结束后结束
join
等待子进程结束再往下进行
互斥锁
acquire()枪锁
release()释放锁
进程间通信
多个进程修改同一个数据时,用锁
1.1 牺牲速度,保证数据安全
1.2 可能造成死锁,效率低
IPC通信机制:队列+管道
2.1进程队列
Queue
JoinableQueue
get、put、full、empty、get_nowait
2.2生产者消费者模型
生产者: 生产/制造数据
消费者: 消费/处理数据
解决了什么问题: 供需不平衡
怎么解决:
通过一个阻塞队列,生数据,取数据都通过这个容器
使用队列Queue
使用JoinableQueue
task_done
join
进程池
是什么
- 定义一个池子,里面放上多个进程,有任务来,用来进程池中的进程处理任务.处理完毕,进程重新放入池中。
- 池中的进程都被用完了,还有任务来,就等待,直到有进程执行完毕重新回到池子,然后使用进程处理任务
- 池中数量固定,都在用就要等待进程空闲。
- 降低操作系统调度难度,减少创建开销、时间。
怎么用
from multiprocess import pol
p=Pool(3) 创建三个进程
同步调用:res=p.apply(work,args=(i,))
异步调用:
- poll.map(func,range(100))
poll.map(work,[(1,2),‘alex‘]) :map自带join
(带可迭代参数,异步调用进程,开启100个任务,map自带join的功能)
- res=p.apply_async(work,args=(i, ))
(带其他参数)
为什么要用?
开启和销毁进程,开销大
线程
是什么
- 进程想要执行任务需要依赖线程,每个进程至少有一个线程
- 执行单位,CPU最小调度单位
为什么要有线程
- 开进程,申请内存空间,耗资源
- 开线程消耗低于开进程
两种创建方式
from threading import Thread
继承Thread,重写run 方法
t=Thread(targs=sayhi,args=(‘太白‘,))
多线程
一个进程内存在多个线程
多线程无法利用多核优势,为什么要有多线程
计算密集型
单核:使用多线程
多核:使用多进程
IO密集型
单核:使用多线程
多核:使用多线程
全局解释器GIL
- 多个线程共享同一份数据不安全
- GIL锁保证任意时刻只有一个线程在解释器中运行
- 只有Cpython解释器有GIL,本质是一把锁
- 多个线程想要执行必须先抢到GIL锁,抢到之后才能运行,释放锁后,其他线程继续抢
event事件
当线程需要通过判断另一个线程的状态才能执行(下一步的时候使用)
协程
进程、线程、协程
进程:资源单位
线程:执行单位
协程:单线程实现并发(程序员YY出来的概念)
用法
遇到IO操作时,程序员自己检测IO并用代码切换,欺骗操作系统,让他误以为你没有IO这个操作
使用gevent模块
monkey.path_all()需要手动配置
g=spawn(任务名):能够识别IO
g.join()等待任务结束
并发
本质:切换+保存状态,开起来像同时运行
效率:
- 计算密集型:降低效率
- IO密集型:提高效率
进程与线程
进程
- 资源单位,资源分配最小单位
- 切换开销大
- 开进程,申请内存空间耗费资源
- 执行单位,CPU调度最小单位
线程
- 切换开销小
- 开线程:多个线程共性资源,开销远低于开进程
守护(进程)线程
等待主进程(主线程)运行完毕,才能被销毁
守护进程:运行完毕指的是主进程运行完毕
守护线程:运行完毕指的是主进程所在的进程内,所有其他非守护线程运行完毕
锁
目的:保护数据安全,当多个进程或多个线程需要修改同一份数据时要用到锁
全局解释器锁GIL
限制任意时刻只有一个线程执行,防止多个线程修改同一份数据
join和互斥锁
- join锁住整个代码
- 锁只锁住修改共享数据的代码
死锁
- 多个进程或多个线程因争夺资源造成互相等待的情况
- 为了解决死锁:使用递归锁RLOCK替代LOCK
- RLOCK维护一个lock和counter变量,直到一个线程的所有acquire都被释放,其他线程才能获得资源
信号量和锁
- 信号量是公共厕所,同时可以进出多个
- 锁是自己马桶,只能进去一个,要想进去,必须等上一个人用完
池
用法:
from concurrent.futures import 1/2
线程池:pool=ThreadPoolExecute(5),参数不传默认CPU个数乘以5
提交任务
同步:两个任务提交,必须第一个完成后第二个才能开始
异步:两个任务提交,必须等待第一个完成才能开始第二个
异步回调:pool.submit(任务,任务参数).add_done_callback(callback回调函数)
pool.shutdown 等待池子中所有代码运行完毕才会往下执行
以上是关于第十二章 并发编程的主要内容,如果未能解决你的问题,请参考以下文章