灵虚御风
醉饮千觞不知愁,忘川来生空余恨!

导航

 

复习

并发编程------复习

框架
1.多道技术--基础
2.进程
3.线程
4.GIL锁
5.进程池与线程池
6.异步同步--指的是任务的执行方式
7.异步回调
8.协程
9.猴子补丁--协程的使用

目录
1.多道技术--基础
----空间复用
----时间复用
2.进程
----为什么使用进程
----进程的两种使用方式(开启子进程)
----守护进程
----常用属性和方法
----僵尸和孤儿进程
----IPC进程间通讯
---------共享文件
---------共享内存区域(主要方式)
------------1.Manager
------------2.Queue
---------管道
---------socket
----互斥锁
----消费者生产者模型
3.线程
----什么是线程?
----线程与进程对比
----守护线程
----使用方式(开启子线程两种方式)
----常用方法
----线程队列(线程间通讯)
------Queue
------lifoQueue堆栈队列,先进后出
------PriorityQueue优先级队列
----线程锁
4.GIL锁
----全局解释器锁
----造成的问题
----带来的好处
----如何避免带来的性能影响
----与自定义锁的区别
----加锁和释放
5.进程池与线程池
----什么是 进程池与线程池
----为什么使用
----使用--创建池
6.异步同步--指的是任务的执行方式
----同步
----异步
----如何实现异步
7.异步回调
----什么是异步回调
8.协程
----什么是协程
----使用协程好处
----协程缺点
----终极方案---进程+线程+协程
----协程与多线程对比
9.猴子补丁--协程的使用
----gevent需自己安装
------1.先打补丁
------2.提交任务
------3.必须保证主线程不会结束---join使用


内容:详解
 
1.多道技术
---- 空间复用
同一时间在内存中同时加载多个程序数据,其内存之间相互隔离
----时间复用
切换+保存状态
切换的两种情况:
​     1.一个进程遇到了IO操作时,切换到另一个进程,
​     2.时间片用完后,也会被强行切换 
总结:多道技术的出现使计算机可以并发执行任务 
 
2.进程:
正在运行的程序 ,是一个资源单位, 包含程序运行的所有资源 
----为什么使用进程:
​ 为了并发的执行多个任务,例如TCP中客户端的并发处理 
----两种使用方式 
​    创建Process实例
​    继承Process类
注意:开启进程的代码必须放在 判断下面 , 因为windows平台开启子进程时,会导入代码执行一遍 来回去要执行的任务 
 
----守护进程:
​ 被守护进程结束时守护进程也会随之结束 
----常用属性和方法
​ join  提高子进程的优先级 使得子进程先于父进程执行  父进程需要等待子进程完成后才能继续执行 
​ daemon 设置为守护进程
​ is_alive 是否存活 
​ pid   进程id
​ terminate  终止进程 
​ exitcode 获取进程的退出码
​ name 名字 
----僵尸和孤儿
孤儿  父进程先于子进程结束了,子进程会被操作系统接管 
僵尸  在linux有一个机制,可以保证父进程在任何时候都可以访问到子进程的一些信息,所以子进程结束后并不会立即清除所有数据 ,这时候就是僵尸进程 
僵尸进程会占用一些系统资源,需要父进程调用waitpid来进行清理, 
python会自动清理僵尸进程 
 
---- IPC
​ 进程间通讯 
​ 因为每个进程之间内存是物理隔离,很多时候我们需要将数据讲给另外一个进程 ,例如:美团要把订单信息交给支付宝
​    1.共享文件
​  特点:数据量没什么限制,但是读写速度慢 
​    2.共享内存区域
​  特点:数据量不能太大,速度快   
​    3.管道
​  单向通讯,传输的是二进制 
​    4.socket 
​   编程较复杂
   主要方式:共享内存
​       1.Manager 提供一系列常用的数据结构,但是没有处理锁的问题 
​       2.进程Queue  是一种特殊的容器,先进先出,并且进程队列支持IPC    已经处理好锁了
---- 互斥锁
​ 相互排斥的锁,mutex 
​ 锁是什么: 本质就是一个标志,可以限制代码是否能够执行 
​ 为什么需要锁:多个进程要操作同一个资源时,可能造成数据错乱 
​ 加锁会导致并发变成串行,降低了效率,保证了数据安全 
​ 
-----与join的区别 
​ join会使得整个进程代码全部串行,并且主进程也无法继续执行 
​ 锁可以控制部分代码串行,其余任然并发,效率比join高  
​ 锁的粒度越小效率越高
 
-----消费者生产者模型 
​ 要解决的问题,生产者用户消费者处理能力不平衡,  如果串行执行任务 效率低下
​ 解决的方案:
​    1.将生产者与消费者节考耦合
​    2.将双方并发执行 
    3.提供一个共享的容器 
​    4.生产者将数据放入容器
​    5.消费者从容器获取数据来处理 
​    
   是否可以使用多线程来完成生产者消费者模型  必须可以
   在线程中需不需要使用队列呢??         建议使用 
 
-------抢票案例:
​ 数据出了问题,一张票 卖给了多个人  ,原因就是因为并发了 
​ 加锁解决:  将并发修改的代码加锁变成串行
3.多线程
----- 线程是: CPU最小的执行单位 ,(操作系统最小调度运算单位),是一个固定执行过程的总称
  一个进程至少包含一个线程,称之为主线程 ,是由操作系统自动开启的 
​ 运行过程中自己开启的线程 称之为子线程
​ 线程间没有父子关系   ,  例如a-b-c   b和c都是a的子线程 
​ 
-----线程对比进程:
​  开启速度快,开销小 
​  同一个进程中所有线程数据共享
 
---- 守护线程:
​  守护线程会在所有非守护线程结束时随之结束,当然守护线程可能提前结束了 
​ 
----  使用方式:
​  与进程一样,开线程的代码可以放任何位置
----  常用方法:
​  currentthread()  获取当前的线程对象
​  enumerate()   获取所有运行中的线程对象
​  active_count   获取存活的线程数量
​ 
----- 线程队列
​ queue 
​  Queue  普通队列
​  LifoQueue  先进后出队列 模拟堆栈
​  PriorityQueue  优先级队列    可以比较大小的数据都能存到其中   取出时按照从小到大取出
​   运算符重载  可以使自定义对象支持 算术运算符     
-----线程锁
​ Lock 互斥锁
​ Rlock 递归锁    同一个线程可以多次锁定或解锁 ,锁了几次就解几次 
​ semaphore  信号量   可以限制同一时间多少线程可以并发执行
​ 死锁问题  当一个资源的访问,需要具备多把锁时,然而不同的锁被不同线程持有了,陷入相互等待中 
​  1.,尽量使用一个锁 , 设置超时 释放手里的锁, 
​  2.抢锁时 按顺序抢 
4.GIL锁
​------ GIL全程 全局解释器锁 ,是一把互斥锁,是非常重要的,为了防止多个本地线程同一时间执行python的字节码,
​ 因为Cpython的内存管理不是线程安全的(非线程安全的),越来越多的特性依赖于这把锁, 如果去掉这个锁的话, 会有很代码需要重构, 并且需要程序自己来处理很多的安全问题,这是非常复杂的 
------造成的问题
​ cpython多个线程不能并行,丧失了多核优势 
----- 带来的好处
​ 保证了线程安全,
----- 如何避免带来的性能影响
​ 首先判断任务的类型 ,分IO密集型   计算密集型 
​ 是IO密集型任务,使用多线程即可,由于大部分时间消耗在IO等待上了,所以影响不大
​ 计算密集型,只能开启多进程 
------与自定义锁的区别
​ GIL只能 保证解释器级别数据安全,如果我们自己开启了一些不属于解释器的资源例如文件. 必须自己加锁来处理 
-------加锁和释放
​ 拿到解释器要执行代码时立即加锁
​ 遇到IO时解锁    
​ CPU时间片用完了   注意解释器的超时时间 与CPU的超时时间不同   为100nm 
 
5.进程池  线程池 
​ 池即容器
​ 线程池 即存储线程的容器 
​ -----为什么使用线程池
​  1.可以限制线程 数量      通过压力测试 来得出最大数量 
​  2.可以管理线程的创建以及销毁 
​  3.负责任务的分配 
​ -----使用
​  创建池
​  submit提交任务   异步任务    将返回future对象    调用add_done_callback 可以添加回调函数 
​  在任务结束时还会自动调用 回调函数并传入 future本身, 调用result()可以拿到任务的结果 
​  
​  不常用的两种方式
​  shutdown  可以关闭线程池,会阻塞直到所有任务全部完成 
​  直接调用result  如果任务没有完成会进入阻塞状态  
​ 
6.  异步同步 
​ 同步: 任务提交后必须原地等待任务执行结,才能继续执行 
​ 异步: 提交任务后可以立即执行后续代码  
​ 异步效率高
​ ----如何实现异步:
​  多线程,多进程
​ -----同步异步指的是   任务的执行方式 
​ 
 
7.异步回调
​ 异步回调  本质就是一个普通函数,该函数会在任务执行完成后自动被调用 
​ 线程池, 谁有空谁处理
​ 进程池,都是在父进程中回调 
 
8. 协程
​ 协程本质是在单线程下实现并发
​ 协程是一种轻量级线程,也称为微线程,可以由应用程序自己来控制调度
 
​ -----好处:
​  可以再一个任务遇到IO操作时,自主切换到自己进程中其他线程   
​  如果任务足够多,就可以充分利用CPU的时间片 
​  
​ 
​ -----缺点:
​  仅适用于IO密集型任务 ,计算密集型,如果是单线程话的串行效率更高 ,建议使用多进程来处理 
​  当单个任务耗时较长时  协程效率反而不高
​   
我们目的就是尽可能的提高效率 
​ 进程 线程  协程 
​ 可以使 多进程 + 线程 + 协程      UWSGI
协程对比多线程:
​ 线程池可以解决一定的并发数量,但是如果并发量超过了机器能承受最大限制,线程池就出现瓶颈了
​ 
9.猴子补丁-- 协程的使用
​ gevent   需要自己安装 
​ 1.先打补丁     (本质是将原本阻塞的代码替换成非阻塞的代码)
​ 2.gevent.spawn(任务)  来提交任务 
​ 3.必须保证主线不会结束 使用join  或是join  all
 
 
今日内容:
IO模型
1阻塞IO  
2.非阻塞IO 
3. 多路复用
4.异步IO
# IO模型
​ 模型就是解决某个问题的套路 
​ IO问题:
​  输入输出 
​  我要一个用户名用来执行登陆操作,问题用户名需要用户输入,输入需要耗时,  如果输入没有完成,后续逻辑无法继续,所以默认的处理方式就是 等
​ 将当前进程阻塞住,切换至其他进程执行,等到按下回车键,拿到了一个用户名,再唤醒刚才的进程,将状态调整为就绪态
 
以上处理方案  就称之为阻塞IO模型
 
存在的问题:
​ 当执行到recv时,如果对象并没有发送数据,程序阻塞了,无法执行其他任务
解决方案:
​ 多线程或多进程,
​   当客户端并发量非常大的时候,服务器可能就无法开启新的线程或进程,如果不对数量加以限制 服务器就崩溃了
​ 线程池或进程池
​  首先限制了数量 保证服务器正常运行,但是问题是,如果客户端都处于阻塞状态,这些线程也阻塞了
​ 协程:
​  使用一个线程处理所有客户端,当一个客户端处于阻塞状态时可以切换至其他客户端任务  
 
## 非阻塞IO模型
​ 阻塞IO模型在执行recv 和 accept  时 都需要经历wait_data 
​ 非阻塞IO即 在执行recv 和accept时 不会阻塞 可以继续往下执行
​ 
​ 如何使用:
​  将server的blocking设置为False 即设置非阻塞 
​  
​ 存在的问题 :
​  这样一来 你的进程 效率 非常高 没有任何的阻塞
​  很多情况下 并没有数据需要处理,但是我们的进程也需要不停的询问操作系统  会导致CPU占用过高
​  而且是无意义的占用 
案例:
服务器:
import socket
import time
server = socket.socket()
server.bind(("192.168.13.103",1688))
server.listen()
server.setblocking(False) # 默认为阻塞    设置为False 表示非阻塞
# 用来存储客户端的列表
clients = []
# 链接客户端的循环
while True:
    try:
        client,addr = server.accept()   # 接受三次握手信息
        # print("来了一个客户端了.... %s" % addr[1])
        # 有人链接成功了
        clients.append(client)
    except BlockingIOError as e:
        # print("还没有人连过来.....")
        # time.sleep(0.5)
        # 服务你的客人去
        for c in clients[:]:
            try: # 可能这个客户端还没有数据过来
                # 开始通讯任务
                data = c.recv(2048)
                c.send(data.upper())
            except BlockingIOError as e:
                print("这个客户端还不需要处理.....",)
            except ConnectionResetError:
                # 断开后删除这个客户端
                clients.remove(c)
        print("=======================",len(clients))
```
 
# 多路复用IO模型
​ 
import socket
import select
server=socket.socket()
server.bind(("127.0.0.1",1688))
server.listen()#三次握手未完成,半连接数
rlist=[server,]
'''
# 将需要检测(是否可读==recv)的socket对象放到该列表中
# accept也是一个读数据操作,默认也会阻塞 也需要让select来检测
# 注意 select最多能检测1024个socket 超出直接报错 这是select自身设计的问题 最终的解决方案epoll
'''
wlist=[]
'''
# 将需要检测(是否可写==send)的socket对象放到该列表中
# 只要缓冲区不满都可以写
'''
#存储需要发送的数据,等待select 检测后,再进行发送
msgs=[("socket","msg")]
print("start")
while True:
# 会阻塞等到 有一个或多个socket 可以被处理
readable_list,writeable_list,_=select.select(rlist,wlist,[])
print("%s个socket可读"%len(readable_list),"%s个socket可写"%len(writeable_list),)
"""
readable_list 中存储的是已经可以读取数据的socket对象 可能是服务器 可能是客户端
"""
#处理可读列表
for soc in readable_list:
if soc==server:
#服务器的处理
client,addr=server.accept()
#将新连接socket对象加到待检测列表
rlist.append(client)
else:
try:
#客户端的处理
data=client.recv(1024)
if not data:
# 如果对方下线 关闭socket 并且从待检测列表中删除
soc.close()
rlist.remove(soc)
continue
# 不能直接发 因为此时缓冲区可能已经满了 导致send阻塞住, 所以要发送数据前一个先这个socket交给select来检查
# soc.send(data.upper())
if soc not in wlist:
wlist.append(soc)
# 将要发送的数据先存起来
msgs.append((soc, data))
except ConnectionRefusedError:
soc.close()
# 对方下线后 应该从待检测列表中删除 socket
rlist.remove(soc)
wlist.remove(soc)
# 处理可写列表
for soc in writeable_list:
# 由于一个客户端可能有多个数据要发送 所以遍历所有客户端
for i in msgs[:]:
if i[0] == soc:
soc.send(i[1])
# 发送成功 将这个数据从列表中删除
msgs.remove(i)
# 数据已经都发给客户端 这个socket还需不需要检测是否可写,必须要删除
wlist.remove(soc) # 否则 只要缓冲区不满 一直处于可写 导致死循环
 客户端:一样
client=socket.socket()
client.connect(("127.0.0.1",1688))
while True:
msg=input("msg:")
if not msg:continue
client.send(msg.encode("utf-8"))
data = client.recv(2048).decode("utf-8")
print(data)
​ 
 晚上总结:
'''
IO 模型

网络传输数据的两个阶段

send 从应用程序copy到操作系统
recv 等待数据到达缓冲区 wait_data 然后在从操作系统缓冲区copy应用程序
wait_data 耗时最长
我们的目的就等待数据的这一段时间 合理的利用CPU来提高效率

阻塞IO
默认情况下就是阻塞IO模型
当执行recv时 如果对方没有数据到达 那么程序阻塞在原地
线程池 有最大限制 不能无限的开线程
来了1000 所以开了1000线程 导致1001 客户端不能正常访问
而且很有可能很多线程处于阻塞状态,浪费了资源
可以使用协程
协程是单线程并发处理,
检测IO操作 当发生IO操作时 切换到其他任务来执行
协程的原理是把原本阻塞的操作 换成非阻塞的操作


非阻塞IO === 非阻塞的网络IO 了解
非阻塞 即 即使遇到了IO操作 也不会阻塞在原地 会继续往下执行
server 是一个服务器socket对象
server.setblocking(Fasle) 设置为非阻塞
问题是: 每次读取数据时 不一定有数据 为了能够及时处理数据 只能不停的询问 忙轮询
这种忙轮询的方式,即使没有数据需要处理 也需要不停的循环,造成了无用CPU占用

多路复用
假设原本有30个socket 需要我们自己来处理, 如果是非阻塞IO模型,相当于从头开始问道尾,如果没有需要处理的
回过头来再次重复,

多路复用解决问题的思路,找一个代理即select,将你的socket交给select来检测
select 会返回 那些已经准备好的 可读或者可写的socket
我们拿着这些准备好的socket 直接处理即可

对比线程池
避免了开启线程的资源消耗
缺点:
同时检测socket不能超过1024


异步IO
阻塞IO recv accept 会将当前线程阻塞住 同步
非阻塞IO recv accept 不会阻塞当前线程 ,没有数据直接抛出异常
分析 属于同步还是异步?
recv (wait_data,copy_data) 设置为非阻塞之后 wait_data不会再阻塞
但是copy_data 也是IO操作 还是会阻塞
也属于同步

多路复用 也属于同步IO


同步 异步 任务的执行方式
同步IO 执行IO任务的方式
异步IO

异步IO
线程池中的submit 就是异步任务
异步的特点就是 立马就会返回
同步翻译为sync 异步async
 
 
​  
​ 
 
 
​ 
​ 
 
​  
 
 
​ 
 
 
​ 
 
 
​ 
 
​  
 
​ 
 
​ 
 
 
 
 
 
 
 
 
 
 
​ 
 
 
 
 
 
​ 
 
 
​ 
 
​ 
 
​ 
 
​   
​   
 
 
 
 
 
 
 
​ 
 
 
 
 
 
 
 
 
 
 
 
​ 
 
 
​ 
 
 
​ 
 
 
 
​ 
 
 
​  
 
 
 
 
 
 
​  
​ 
 
 
 
 
 
 
 
 
 
 
 
 
 
​ 
 
 
​ 
 
 
 
 
 
 
​  
 
 
 
 
 
​ 
 
 
 
 
 
 
 
 
 
 
 
#### 
 
posted on 2019-06-10 22:09  没有如果,只看将来  阅读(211)  评论(0编辑  收藏  举报