并发编程理论回顾20点

1、多道技术
1.空间复用
同一时间在内存中存放多个程序 内存相互隔离
2.时间复用
CPU在遇到IO时切换到另一个程序可以实现并发切换+保存状态

2、进程理论
进程是一个资源单位 包含了该程序运行所需的所有资源
为什么使用它? 为了提高程序的执行效率 当遇到IO阻塞时 或需要同时执行多个任务时

linux 和 windows创建进程的区别
linux下创建进程 会直接将父进程的所以数据复制一份给子进程
windows创建进程时 子进程会加载父进程的字节码(没有任何的多余符号)

3、进程的使用
1.实例化Process这个类 传入一个target参数
2.继承Process 实现run函数

4、IPC进程间通讯

为什么使用IPC? 进程间的内存时物理隔离的 无法直接访问数据
实现方式
1.共享文件 速度慢 没有锁
2.管道 单向通讯 需要有父子关系 没有锁
3.共享内存 速度快 数据量较小 Manager没有锁 Queue有锁(先进先出)

5、守护进程

A进程守护B进程 B 进程结束 A也结束 皇帝死了 妃子陪葬


6、互斥锁 死锁 可重入锁 信号量 (抢票代码 死锁代码)

1.问题? 多个进程同时读写同一份数据时 可能造成数据混乱 (本地IO速度很快 基本不会出现问题)

读数据没有必要加锁写数据必须加

锁的特点:并发改串行被锁的代码将会变成串行 效率降低

锁的原理:就是加上一堆判断 锁相当于一个标记

互斥锁 相互排斥 就像在宿舍大家共用一个厕所 一个使用中 其他人不能用同一线程不能多次acquire 会卡死。

2.死锁?
死锁造成的问题.程序卡死一个锁不会产生死锁
当有多个锁多个线程时会产生死锁
a b 锁
p k 线程
当p 和 k 都需要a和b锁时才可能产生死锁

3.可重入锁RLock
同一个线程可以多次执行acquire 执行一次acquire 计数加1
执行一次release 次数减一 执行acquire的次数需要与release的次数对应
在执行被锁的代码时 同一个线程 不会判断次数 其他线程需要判断 计数为0才可以执行

不是用来解决死锁的

4.信号量
案列:
sem = semaphore(2)
acquire
code.....
release
开了十个线程 只能有两个同时执行

信号量作用:限制同时执行被锁代码的线程数量

7、生产者消费者模型
while True:
生产数据 1
处理数据 10
生产数据 和 处理数的能力不匹配 一个快一个慢 整体下变低

学习多进程 可以将生产和处理 分到不同进程中 来解决能力不匹配的问题 快的多干点 慢的少干点
第二个问题 作为处理数据的一方不知到什么时候会有数据 两个进度不同 使用一个共享的数据容器

将生产和消费分到不同进程(线程)中 使用共享数据容器来同步数据 详见思聪吃热狗的案例


8、线程理论
线程是CPU的基本执行单位一连串的代码就是像流水线cpu会按照顺序依次执行他们

为什么用? 需要实现并发执行任务 并发任务进程也可以完成

线程和进程的区别
1.资源开销
进程开销大 线程开销小
2.数据共享
一个进程内的所有线程数据共享
举例:
一个进程中 必然包含一条主线程
一个进程可以包含多个线程
进程是工厂 线程 是流水线
一个工厂有多个流水线 至少得有一个 所有流水线都可以使用工厂内的资源

9、线程的使用 (生产者消费者代码 抢票代码)
使用线程
1.实例化Thread类 参数target传入任务
2.继承Thread类 实现run函数

10、守护线程
守护线程 会在被守护线程结束时一并结束
与守护进程的区别
a 守护 b 同时还有另一个线程 c
a 会等到 b 和 c都结束才结束
皇后 守护皇帝
皇宫里还有太子
皇后会等到皇帝和太子都死了 才死
守护线程会等待所有非守护线程结束后才算结束
main
sub1
sub2
sub3

sub1.deamon = True
sub1会等待 main sub2 sub3 全都结束 才会结束

11、GIL(Global Interpreter Lock)
问题: 一个py程序 要想运行 必须运行解释器 解释器的工作时翻译代码 并执行
当一个py进程中 有多个线程 线程的任务就是执行代码 意味者 多个线程都要使用解释器
简单的说 多线程会争抢解释器的执行权
如果是自己开的线程 多线程要访问相同数据 加锁就能解决
但是有一些代码不需要程序员写的 也确实需要共享使用 就是解释器

GC:垃圾回收器 负责清理内存中的无用数据 清理垃圾也需要执行代码 但是GC不应该卡住用户的代码执行
只能开线程
GC 看到 x = 10 x = 1 准备删除10 这时候突然CPU切到用户线程 a = 10 此此时还没有问题
紧接着 CPU 又切到GC GC上来就删除10 在切到用户线程 a 所指向的地址被清理了 产生错误

解决方案: 给解释器加上锁 保证GC执行期间 用户线程不能执行

全局解释器锁带来的问题
同一时间只有一个线程能使用解释器 无法利用多核CPU
那Cpython 多线程是鸡肋吗?
当程序是IO密集时 多线程能提高效率 IO的速度 明显要比CPU执行速度慢
当程序时计算密集时 多线程无法提升效率 得使用多进程

12、GIL 和 自定义锁
相同点:都是互斥锁
不同点:
GIL解释器级别锁 锁的时解释器代码
自定义锁 锁的是自己写的代码
自动加锁只要有线程在使用解释器 自动解锁 1.IO 2.执行时间过长3ms 3.线程执行结束
有了GIL 为什么还需要自定义锁?
GIL 不清楚什么代码会造成数据竞争问题 不知道什么地方该加

13、进程池,线程池
池是一种容器进程池里面装的是进程
为什么需要这个容器?
当程序中有多个进程时 管理变得非常麻烦进程池可以帮我们管理进程
1.进程的创建
2.进程的销毁
3.任务的分配
4.限制最大的进程数 保证系统正常运行
使用方式?
ThreadPoolExecutor 线程池
实例化 时指定最大线程数
ProcessPoolExecutor 进程池
实例化 时指定最大进程数
执行submit来提交任务

14、队列 queue

这个queue和进程里的Queue不同 就是一个简单的容器
队列是一种数据的容器
特点:先进先出
queue先进先出
lifoqueue先进先出
priorityqueue 优先级队列 整型表示优先级 数字越大优先级越低

15、同步异步 阻塞 非阻塞 概念
同步提交任务需要等待任务执行完成才能继续执行
异步提交任务不需要等待任务执行 可以立即继续执行
指的都是提交任务的方式

阻塞遇到IO 失去了CPU执行权 看上去也是在等 与同步会混淆
非阻塞就绪,运行,代码正常执行

阻塞非阻塞指的是线程状态
线程的三种状态 就绪 运行 阻塞

16、异步回调
发起了一个异步任务 任务完成后回来调用指定的函数
pool = ThreadPoolExecutor()
f = pool.submit(task)
f.add_done_callback(函数名)

不需要等到任务结束 继续执行下一行
什么是异步回调 在发起异步任务后 子线程或子进程 完成任务后需要通知任务发起方 如果通知就调用一个函数 add_done_callback(函数名)
为什么需要异步回调
由于任务是异步执行 任务发起方不知道什么时候完成 所以使用回调的方式来告诉发起方任务执行结果

17、协程
协程 指的是 单线程实现并发
为什么用协程? 多线程实现并发 有什么问题?
TCP程序中 处理客户端的连接 需要子线程 但是子线程依然会阻塞 一旦阻塞 CPU切走 但是无法保证是否切到当前程序
提高效率的解决方案 是想办法尽可能多的占用CPU 当程序遇到阻塞时 切换到别的任务 注意使用程序内切换

协程的使用
1.生成器
2.greenlet 封装了生成器 不能检测到IO行为
3.gevent 封装了grennlet 既能够切换执行 也能检测IO
# gevent 需要配合monkey补丁 monkey补丁内部将原本阻塞的模块 替换为了非阻塞的
# monkey必须放在导入(需要检测IO的模块)模块之前
monkey.patch_all()
gevent核心函数spawn(函数名)
join让主线程等待所有任务执行完成才结束

18、IO模型
网络传输的两个阶段
waitdata(recv accept) copydata(send)
一个应用程序的缓存 和系统缓存
recv accept 先 wait 在copy
send 只有copy
阻塞IO
之前所提到的,除了协程 都是阻塞 IO
应用程序 发送 系统调用 操作系统等待数据(wait) 数据准备好 return data

非阻塞IO
recv send accept 都不会阻塞 会立即执行 但是不能保证立马就有数据 没有数据抛出异常
我们需要手动捕获异常 捕获异常后可以处理别的任务  CPU的利用率提高了  但是同时也浪费了CPU 当没有任何数据处理的时候 就在空转

多路复用
管理连接的一种方式
为什么使用它? 相对于非阻塞IO降低无用的系统调用
怎么管?
核心函数select 默认时阻塞的 阻塞到有任意一个连接可以被处理
一 创建连接 和管理连接

1.创建服务器socket对象
2.将服务器对象交给select来管理
3.一旦有客户端发起连接 select将不在阻塞
4.select将返回一个可读的socket对象列表(第一次只有服务器)
5.服务器的可读代表有连接请求 需要执行accept 返回一个客户端连接conn 由于是非阻塞 不能立即去recv
6.把客户端socket对象也交给select来管理 将conn加入两个被检测的列表中

7.下一次检测到可读的socket 可能是服务器 也可能客户端 所以加上判断 服务器就accept 客户端就recv
8.如果检测到有可写(可以send就是系统缓存可用)的socket对象 则说明可以向客户端发送数据了
7 和 8 执行顺序不是固定的
处理数据收发
两个需要捕获异常的地方
1.recv 执行第7步 表示可以读 为什么异常 只有一种可能客户端断开连接
还需要加上if not 判断是否有数据 ;linux下 对方下线不会抛出异常 会收到空消息
2.send 执行第8步 表示可以写 为什么异常 只有一种可能客户端断开连接 

异步IO
IO包括 网络IO 本地IO
上面的三种描述的都是网络IO
不能本地IO的问题
解决的方案就是:
将同步的IO操作改成异步的IO操作 在IO期间 可以执行其他的任务
使用asyncio模块


19、socketserver
是什么? 对服务器端的socket的封装
封装了多线程和socket
为什么用? 简化代码
使用方法:
socketserver (forkingUDP forkingTCP windows无法使用)
核心类 ThreadingUDPServer ThreadingTCPServer
ThreadingTCPServer 实例化时 传入服务器地址 和 自定义的一个数据处理类
自定义类需要继承BaseRequestHandler类中需包含handle函数
对象调用serve_forever

20、Event
是什么?  线程间通讯的方式
为什么用?  简化代码
set()设置为True
wati()阻塞 直到为True

 

 

原文地址:https://www.cnblogs.com/wanlei/p/9970302.html