4 May 18
复习:
1、 串行不是单纯的一个一个来,串行是按照规定好的顺序一个一个来
2、 listen(5): 规定的是最大的请求数;池: 规定的是最多连接数
3、 网络IO:收信息(wait + copy)和发消息(copy)
4、 多路复用IO可调用模块:
a、 select:本质是循环的问操作系统,随着元素的增加,效率降低
b、 poll:可以放下比select多的对象。但本质还是循环的问操作系统,随着元素的增加,效率降低。
c、 epoll:每个套接字都绑定一个回调函数,变被动为主动。Windows不支持epoll
d、 selector(更高程度的封装)为当前计算机在以上三种模块中选择效率最高的
5、 维持高并发的高效率模式:多进程+多线程+单线程下并发。例: BS架构中B/Nginx(sever)用的模式既以上模式
socketserver:
1、 多线程调: ThreadingTCPServer 或ThreadingUDPServer
2、 多进程调: ForkingTCPServer 或 ForkingUDPServer (Windows系统无法调用)
3、 ThreadingTCPServer: while循环+accept+启线程
4、 ThreadingUDPServer: while循环+recvfrom+启线程
5、 ThreadingTCPServer/ ThreadingUDPServer 传参时,bind&activate=True等价于bind + listen
6、 对TCP部分,用socketserver产生的self.request 等价于用socket产生的conn
7、 对UDP部分,用socketserver产生的self.request,为一个小元组,该元组的第一个元素为接收到的信息,第二个元素等价于用socket产生的server
8、 源码查看:
a、 主要的类分为三种类型(server(连接循环),request(通信循环),mixin(并发相关))
b、 看源码从执行入口开始看
c、 查看源码逻辑,可后续利用第三方源码封装自己的工具
9、 基于tcp
服务端:
import socketserver
# 通信循环
class MytcpHandler(socketserver.BaseRequestHandler):
def handle(self): #socketserver规定的,必须使用
while True:
try:
data = self.request.recv(1024) # 1024 接收数据的最大限制
if not data: break # 针对linux系统
self.request.send(data.upper()) # 注意:收发都是以bytes为单位
except ConnectionResetError:
break
self.request.close()
if __name__ == '__main__':
#连接循环
server=socketserver.ThreadingTCPServer(('127.0.0.1',8080),MytcpHandler)
server.serve_forever()
print(server.server_address)
print(server.RequestHandlerClass)
print(server.socket)
客户端:
import socket
client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(('127.0.0.1',8080))
while True:
msg=input('>>: ').strip()
client.send(msg.encode('utf-8'))
data=client.recv(1024)
print(data.decode('utf-8'))
client.close()
10 基于UDP
服务端:
import socketserver
# 通信循环
class MyUDPHandler(socketserver.BaseRequestHandler):
def handle(self):
# print(self.request)
res=self.request[0]
print('客户端发来的数据:',res)
self.request[1].sendto(res.upper(),self.client_address)
if __name__ == '__main__':
#连接循环
server=socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyUDPHandler)
server.serve_forever()
客户端:
import socket
import os
client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
msg='%s hello' %os.getpid()
client.sendto(msg.encode('utf-8'),('127.0.0.1',8080))
res,server_addr=client.recvfrom(1024)
print(res)
并发编程知识点总结
一 网络编程
目标:编写一个C/S或B/S架构的基于网络通信的软件
1、C/S,B/S(*****)
server<===============>client
服务端特点:
1、不间断地提供服务
2、服务端要支持并发+高性能
2、互联网
互联网=物理连接介质+互联网协议(OSI七层***)
tcp三次握手,四次挥手 (*****)
tcp可靠,但不如udp效率高 (*****)
udp不可靠,但效率高 (*****)
3、socket(*****)
socket抽象层位于传输层与应用层之间
4、基于tcp协议的套接字通信(*****)
加上连接循环
加上通信循环
5、粘包问题:(*****)
tcp流式协议独有的粘包问题
解决方法:自定义报头
udp数据报协议没有粘包问题
6、远程执行命令的小程序/上传下载文件的程序(*****)
7、基于udp协议的套接字通信(***)
二 并发编程
目标:让服务端能够支持高并发+高性能
1、 操作系统发展史
多道技术(*****)
产生背景:想要在单核下实现并发
多道技术的核心:
1、空间上的复用(具体指的是内存中同时读入多道程序,多道程序的内存空间是物理隔离)
2、时间上的复用(复用cpu的时间)
切换+保存状态=》并发
切换:
1、遇到IO切换(可以提升效率)
2、运行时间过长或者有一个优先级更高的进程抢走了cpu(反而会降低效率)
2、进程
1、进程理论(*****)
1、进程与程序区别
2、并发与并行
并发:看起来同时运行,单核就可以实现并发,但是单核无法实现并行
并行:真正意义上的同时运行,一个cpu同一时刻只能做一件事
只有多核才能同时做多件事,即并行的效果
串行:按照固定的顺序一个个地执行
3、不同操作系统开启子进程的区别
4、一个进程的三种运行状态
2、开启进程的两种方式(*****)
了解:僵尸进程与孤儿进程(**)
3、守护进程(**)
4、互斥锁与信号量(**)
互斥锁就将并发变成一个一个的执行,牺牲了效率保证了数据安全
只有在多个任务修改共享的数据的时候才会考虑用互斥锁
5、IPC机制:队列,管道(*)
6、进程queue=管道+锁 (***)
7、生产者消费者模型(*****)
1、什么是生产者消费者模型?
模型指的是解决问题的一种套路
该模型中包含两种重要的角色:
生产者:生产数据的任务
消费者:处理数据的任务
2、什么用生产者消费者模型?
在程序中有明显地两类任务,一类负责生产数据,另外一个类则拿到生产的数据进行处理,此时就应该
考虑使用生产者消费者模型来处理这种问题
2、为什么要用生产者消费者模型?好处?
1、将生产者与消费者解开耦合
2、平衡了生产者的生产数据的能力和消费者处理数据的能力
原理:
解耦和指的是生产者不与消费者直接打交道,
生产者可以不停地往队里里放数据
消费者可以不停地从队列里取走数据进行处理
3、如何实现?
3、线程
1、线程理论(*****)
1、开一个进程内默认就有一个线程
2、线程vs进程
1、同一进程内的多个线程共享进程内的资源
2、创建线程的开销要远小于进程
2、开启线程的两种方式(*****)
3、守护线程(**)
4、互斥锁与信号量(**)
5、GIL vs 互斥锁(*****)
1、什么是GIL
GIL是全局解释器锁,是加到解释器身上的,
同一进程内的所有的线程,但凡执行,必须拿到解释器执行才能之心个,要拿到解释器必须先抢GIL
所以GIL可以被当做执行权限
2、GIL的影响
GIl会限制同一进程的内的多个线程同一时间只能有一个运行,也就说说python一个进程内的多线线程
无法实现并行的效果,即无法利用多核优势
然后多核提供的优势是同一时刻有多个cpu参与计算,意味着计算性能地提升,也就是说我们的任务是
计算密集型的情况下才需要考虑利用多核优势,此时应该开启python的多进程
在我们的任务是IO密集型的情况下,再多的cpu对性能的提升也用处不大,也就说多核优势在IO密集型程序面前
发挥的作用微乎其微,此时用python的多线程也是可以的
3、GIL vs 互斥锁
GIL保护的是解释器级别的数据
本质就是一个互斥锁,然后保护不同的数据就应该用不同的互斥锁,保护我们应用程序级别的数据必须自定义互斥锁
运行流程?
6、死锁现象与递归锁(**)
7、线程queue(***)
8、Event事件(**)
4、池(*****)
为何要用池:
操作系统无法无限开启进程或线程
池作用是将进程或线程控制操作系统可承受的范围内
什么时候用池:
当并发的任务数要远超过操作系统所能承受的进程数或
线程数的情况应该使用池对进程数或线程数加以限制
如何用池?
池内装的东西有两种:
装进程:进程池
装线程:线程池
进程线程池的使用
提交的两种方式:
同步调用
异步调用+回调机制
tpool=tpool=ThreadPoolExecutor(3)
tpool.submit(task,arg1,agr2....).add_done_callback(handle)
任务执行的三种状态:
阻塞
阻塞
非阻塞:
就绪
运行
5、单线程下实现并发(***)
协程:在应用程序级别实现多个任务之间切换+保存状态
高性能:
单纯地切换,或者说么有遇到io操作也切换,反而会降低效率
检测单线程下的IO行为,实现遇到IO立即切换到其他任务执行
gevent
6、网络IO模型(主要掌握理论***)
阻塞IO
非阻塞IO
IO多路复用
异步IO