目录
- 网络编程
- 1. C/S B/S架构
- 2. 互联网通信的原理
- 3. osi七层协议
- 三次握手
- 四次挥手的过程
- 4. TCP 与UDP的区别
- 5. 客户/服务端通信
- 6. 黏包现象
- 7. 系统缓冲器
- 8. 什么情况下产生黏包
- 9. 解决黏包的方案
- 10.文件的上传
- 11. 基于UDP协议的socket
- 12. socketserver
- 13. 进程基础知识
- 14. 操作系统
- 15. 操作系统的发展 ( 多道术 )
- 16. 进程
- 17. pid
- 18. join
- 19. 多进程
- 20. 僵尸/孤儿进程
- 21. 守护进程
- 22. 互斥锁
- 23. 进程之间的通信 : 队列
- 24. 进程之间的通信实例
- 25. 生产者消费者模型
- 26. 线程的理论知识
- 27. 开启线程的两种方式
- 28. 线程与进程的对比
- 29. 线程的其他方法
- 30. 守护线程
- 31. 互斥锁
- 32. 死锁现象 - 解决办法-递归锁
- 33. 信号量
- 34. GIL锁
- 35. 验证Cpython的多进程和多线程的并发效率
- 36. GIL锁与互斥锁的关系
- 37. 进程池线程池
- 38. 阻塞 非阻塞 同步 异步
- 39. 异步 + 调用机制
- 40. 线程队列
- 41. 事件 Event 进程/线程
- 42. 协程
- 43. Greenlet模块
- 44. Gevent
网络编程
1. C/S B/S架构
C : client 客户端
B : Browser 浏览器
S : server 服务端
C/S 客服端与服务器之间的架构: QQ,微信,游戏
优点 : 安全性高,个性化设置,功能去全面
缺点 : 开发成本高 , 维护成本高(app),面向的客户固定
B/S架构: 浏览器与服务器之间的架构 , 输入C/S架构 最近几年比较流行的特殊的C/S架构
优点 :开发成本低, 面向用户广泛
缺点 : 安全性相对低,响应速度相对较慢,
2. 互联网通信的原理
3. osi七层协议
五层体系结构 | 对应的协议 | 每一层的功能 |
---|---|---|
应用层 | HTTP FTP协议 | 规定应用程序的数据格式,为应用程序提供了网络服务。 |
传输层 | TCP/UDP协议 | 建立”端口到端口”的通信,负责向用户提供端到端的通信服务,实现流量控制以及差错控制。 |
网络层 | ARP协议 , IP协议 , IP数据包 | 建立”主机到主机”的通信,创建逻辑链路,以及实现数据包的分片和重组,实现拥塞,控制网络互连等功能。 |
数据链路层 | 以太网协议(Ethernet) (数据报头,帧) | 在通信的实体间建立数据链路连接。 |
物理层 | 连接网络的物理手段,负责传送原始的二进制数据 |
TCP与UDP的区别
TCP | UDP |
---|---|
面向有链接的通信服务 | 面向无链接的通信服务 |
提供可靠的通信服务 | 不可靠,会丢包 |
保证数据顺序 | 不保证数据顺序 |
数据无边界 | 数据有边界 |
速度慢 | 速度快 |
面向字节流 | 面向报文 |
一对一 | 可以一对一,一对多 |
报头至少20字节 | 报头8字节 |
有流量控制,拥塞控制 | 没有 |
三次握手
TCP是面向连接的,无论哪一方向另一方发送数据之前,都必须先在双方之间建立一条连接。在TCP/IP协议中,TCP协议提供可靠的连接服务,连接是通过三次握手进行初始化的。三次握手的目的是同步连接双方的序列号和确认号并交换TCP窗口大小信息。
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack (number )=x+1,随机产生一个值seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为Client发送的seq+1,即x+1;如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送给Server。Server检查ack是否为Server发送的seq+1,即y+1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手。
四次挥手的过程
当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,肯定是要断开TCP连接的,那对于TCP的断开连接,这里就有了神秘的“四次挥手”。
(1)第一次挥手:客户端设置seq=x,向服务端发送一个FIN = 1报文段;此时,主机1进入FIN_WAIT_1状态;这表示客户端没有数据要发送给服务端了;
(2)第二次挥手:服务端收到了客户端发送的FIN=1,向客户端回复ACK=1,ack=收到的seq+1=x+1,然后客户端进入FIN_WAIT_2状态;服务端告诉客户端,在等待自己去关闭连接;
(3)第三次挥手:服务端向客户端发送FIN=1,设置seq=y,请求关闭连接,同时服务端进入LAST_ACK状态;
(4)第四次挥手:客户端收到服务端发送的FIN,向服务端发送ACK=1,ack=收到的seq+1=y+1,然后主机1进入TIME_WAIT状态;服务端收到客户端的ACK报文段以后,就关闭连接;此时,客户端等待2MSL后依然没有收到回复,则证明Server端已正常关闭,客户端也关闭连接。
物理层 : 物理连接介质 ( 网线 ) 发送的数据是010101 比特数据流
数据链路层 : 按照一定的协议对比特流进行分组
物理层: 一系列的物理连接介质: 网线,光纤,电缆等等等. 发送的数据就是010101010110比特数据流,这些数据连续不断地收发,010110,拿到010101没有用,你不知道数据代表的意义, 数据要进行分组(按照一定规则), 数据分组这件事物理层做不了. 数据链路层: 以太网协议 是按照一定的协议对比特流数据进行分组. 以太网协议.:就是对数据进行分组. 写信: 数据交流. 收件人地址, 发件人的地址.信件类型(加急,普通,工作上信封等等.....). 以太网协议: 一组电信号构成一个数据报,叫做‘帧’ 每一数据帧分成:报头head和数据data两部分 数据头(head) | data数据 数据头: 固定长度18个字节. 源地址,目的地址,数据类型. data数据: 46字节 <= data <=1500字节 问题一: 为什么数据头要固定? 固定就是一个标准,统一,为了提取源地址以及目标地址. 问题2: 以太网协议中源目标地址如何设置唯一? 网线直接接触的硬件就是网卡.网卡上有一个地址,mac地址,确定计算机的唯一性的物理地址. 网卡上: 12位 16进制组成的一串数字: 前六位 厂商编号: 后六位:流水线号. 只做好了信件: head(源地址,目标地址,数据类型) | data(今晚你请我吃饭) 广播: 计算机最原始的通信方式就是吼. 数据的分组(源地址目标地址) + 广播: 理论上我的计算机就可以通信了.效率太低,每台计算机都需要接收广播的消息,查看是否是给自己的数据.比广播风暴还要严重. 所以: 广播它是有范围的,在同一子网,局域网内是通过广播的方式,发消息. 网络层: IP协议:确定对方的局域网的位置. 广播,mac地址,+ ip == 可以找到世界上任意一台计算机. 计算机的通信: 计算机的软件与服务器的软件进行的通信. 传输层: 端口协议. 广播,mac地址,+ ip + 端口 == 可以找到世界上任意一台计算机对应的软件. 应用层: 软件自己定义的协议. qq : 发送数据; '今晚请我吃饭...' ---> {id: '念', 'content': '今晚请我吃饭...'} 将数据按照自己定义的协议进行分装, http FTP协议. 五层协议重新梳理: 服务器: 大黑盒子, 机房声音很大,对温度,湿度,等环境都有要求,双电源,双网卡,系统linux. 详细解释中间环节一些特殊的功能: 数据经过以太网协议封装后,先要从局域网内进行吼.每次发消息,每次都要吼,这样效率也是很低的.(数据给交换机,交换机在分发出去.) 交换机的自主学习功能: 广播: 吼. 单播: 单线直接联系. 物理层---> 数据链路层(以太网协议(mac地址)) ---->网络层(IP协议) ----> 传输层(端口协议(TCP,UDP协议)) ---> 应用层: mac地址 + 广播形式 + ip地址 + 端口 == 锁定全世界范围内的任意一个计算机的某软件的位置. ip地址 + 端口 == 锁定全世界范围内的任意一个计算机的某软件的位置. 传输层: 端口协议. TCP协议,UDP协议. 端口: 0~65535端口号. 1~1023系统占用的端口号. 1024~8000之内:一般的是有软件占用. TCP的三次握手四次挥手 客户端 与 服务端第一次建立通信联系 需要三次''握手''. 1563177822311 建立的链接不能一直连接着. TCP协议: 好人协议,不会拒绝别人. syn洪水攻击: 黑客会虚拟很多的假IP,然后访问你的服务器,半连接池,缓冲效果. 四次挥手: 1563178685751 udp与tcp tcp协议: 优点:好人协议,流式协议.稳定,安全, 缺点: 效率低, 使用TCP的应用:Web浏览器;文件传输程序。 udp协议: 优点: 效率高,传输快. 缺点: 不安全,不是面向连接的,不可靠 使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP),微信qq。
4. TCP 与UDP的区别
TCP | UDP |
---|---|
面向有链接的通信服务 | 面向无链接的通信服务 |
提供可靠的通信服务 | 不可靠,会丢包 |
保证数据顺序 | 不保证数据顺序 |
数据无边界 | 数据有边界 |
速度慢 | 速度快 |
面向字节流 | 面向报文 |
一对一 | 可以一对一,一对多 |
报头至少20字节 | 报头8字节 |
有流量控制,拥塞控制 | 没有 |
5. 客户/服务端通信
socket套接字充当的就是内置模块的角色.
# 你说一下socket的套接字?
'''
socket 套接字,它存在于传输层与应用层之间的抽象层,
1. 避免你学习各层的接口,以及协议的使用, socket已经封装好了所有的接口.
直接使用这些接口或者方法即可,使用起来方便,提升开发效率.
2. socket就是一个模块.通过使用学习模块提供的功能,
建立客户端与服务端的通信,使用方便.
'''
单个客服单次通信
服务端 :
import socket
import subprocess
# 1. 创建socket对象
server = socket.socket()
# 2. 绑定ip地址和端口
server.bind(('127.0.0.1',9966))
# 3. 监听
server.listen(5)
# 4. 接收连接
conn,addr = server.accept()
from_client = conn.recv(1024) # 至多接收1024字节
print(f'来自客户端{addr}的消息:{from_client}')
# 5. 发送消息
send_world = input(">>>")
conn.send(send_world.encode('utf-8'))
conn.close() # 关闭通道
server.close() # 关闭服务器
///
客户端 :
import socket
# 1. 创建socket对象
phone = socket.socket()
# 2. 连接服务器ip地址和端口
phone.connect(('127.0.0.1',9966))
# 3. 发送消息
send_world = input(">>>")
phone.send(send_world.encode('utf-8'))
# 4. 接收消息
from_server = phone.recv(1024)
print(f'来自服务器的消息:{from_server.decode("utf-8")}')
# 关机
phone.close()
多客户连续通信
服务器端 :
import socket
import subprocess
# 1. 创建socket对象
server = socket.socket()
# 2. 绑定ip地址和端口
server.bind(('127.0.0.1',9966))
# 3. 监听
server.listen(5)
# 4. 接收连接
conn,addr = server.accept()
while 1:
try:
from_client = conn.recv(1024) # 至多接收1024字节
print(f'来自客户端{addr}的消息:{from_client.decode("utf-8")}')
if from_client.decode('utf-8').upper() == "Q":
break
# 5. 发送消息
send_world = input(">>>")
conn.send(send_world.encode('utf-8'))
except ConnectionResetError:
break
conn.close() # 关闭通道
server.close() # 关闭服务器
------------------------------------------------------------------------------
客户端 :
import socket
# 1. 创建socket对象
phone = socket.socket()
# 2. 连接服务器ip地址和端口
phone.connect(('127.0.0.1',9966))
# 3. 发送消息
while 1:
send_world = input(">>>")
if send_world.upper() == "Q":
phone.send(send_world.encode('utf-8'))
break
phone.send(send_world.encode('utf-8'))
# 4. 接收消息
from_server = phone.recv(1024)
print(f'来自服务器的消息:{from_server.decode("utf-8")}')
# 关机
phone.close()
执行远端命令
服务端 :
import socket
import subprocess
phone = socket.socket()
phone.bind(('127.0.0.1', 8888))
phone.listen(5)
# 4. 接收连接
print('start')
conn, addr = phone.accept()
while 1:
try:
cmd = conn.recv(1024) # dir
obj = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
result = obj.stdout.read() + obj.stderr.read()
conn.send(result)
except ConnectionResetError:
break
conn.close()
phone.close()
------------------------------------------------------------------------
客户端:
import socket
phone = socket.socket()
phone.connect(('127.0.0.1', 8888))
# 发消息
while 1:
cmd = input('>>>').strip()
phone.send(cmd.encode('utf-8'))
# 接收消息
result = phone.recv(1024) # 夯住,等待服务端的数据传过来
print(result.decode('gbk'))
# 关机
phone.close()
6. 黏包现象
黏包 : 多个数据包被连续储存于连续的缓存中,在对数据包进行读取时,由于无法确定发生方的发送边界,而采用某一估测值大小来进行数据读出,若双方的size不一致时就会使发送方发送的若干数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包的数据尾
7. 系统缓冲器
缓冲区的作用:
没有缓冲区:如果你的网络出现短暂的异常或者波动,接收数据就会出现短暂的中断,影响你的下载或者上传的效率.
但是 凡是都是双刃剑,缓冲区解决了上传下载的传输效率的问题,带来了黏包问题.
8. 什么情况下产生黏包
1. recv会产生黏包(如果recv接受的数据量(1024)小于发送的数据量,第一次只能接收规定的数据量1024,第二次接收剩余的数据量)
2.send 也可能发生粘包现象.(连续send少量的数据发到输出缓冲区,由于缓冲区的机制,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络)
9. 解决黏包的方案
服务端 :
import socket
import subprocess
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1',9988))
server.listen(5)
conn,addr = server.accept()
while 1:
try:
cmd = conn.recv(1024)
obj = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
result = obj.stdout.read() + obj.stderr.read()
result = result.decode('gbk').encode('utf-8')
# 1. 制作报头
head_data = {
'md5':"asdfghjgkj",
'file_name':"学习视频",
'file_size':len(result)
}
# 2. 将报头字典转换json
head_data_json = json.dumps(head_data)
# 3. json 转换成bytes
head_data_bytes = head_data_json.encode('utf-8')
# 4. 计算报头长度
head_len = len(head_data_bytes)
# 5.将bytes报头长度转换成固定长度字节(4字节)
head_len_bytes = struct.pack('i',head_len)
# 6.发送固定的字节长度(4字节)- 字典的字节大小
conn.send(head_len_bytes)
# 7. 发送报头 - 字典数据
conn.send(head_data_bytes)
# 8. 发送原始数据
conn.send(result)
except ConnectionResetError:
break
conn.close()
server.close()
客户端:
import socket
import struct
import json
phone = socket.socket()
phone.connect(('127.0.0.1',9988))
while 1:
cmd = input(">>>").strip()
phone.send(cmd.encode("utf-8"))
# 1. 接收字典字节大小
dict_size = phone.recv(4)
dict_size = struct.unpack('i',dict_size)[0]
print(f"字典字节:{dict_size}")
# 2. 接收字典数据
dict_data = phone.recv(dict_size).decode('utf-8')
dict_data = json.loads(dict_data) # 原字典
print(dict_data)
# 3. 接收原数据
total_data = b""
while len(total_data) < dict_data['file_size']:
total_data += phone.recv(1024)
print(total_data.decode('utf-8'))
phone.close()
10.文件的上传
服务端
import socket
import struct
import json
import os
MY_FILE = os.path.join(os.path.dirname(__file__),'my_file')
def socket_server():
server = socket.socket()
server.bind(('127.0.0.1',8848))
server.listen()
conn, addr = server.accept()
# 1. 接收固定长度的4个字节
four_bytes = conn.recv(4)
# 2. 利用struct反解
head_len = struct.unpack('i',four_bytes)[0]
# 3. 接收bytes类型的报头
head_dic_json_bytes = conn.recv(head_len)
# 4. 将bytes类型的报头转化成json
head_dic_json = head_dic_json_bytes.decode('utf-8')
# 5. 将json类型报头转化成字典形式的报头
head_dic = json.loads(head_dic_json)
# 有一个MD5校验
# 6. 接收原始数据
with open(os.path.join(MY_FILE,head_dic['new_file_name']),mode='wb') as f1:
total_size = 0
while total_size < head_dic['file_size']:
every_data = conn.recv(1024)
f1.write(every_data)
total_size += len(every_data)
conn.close()
server.close()
socket_server()
客户端
import socket
import os
import json
import struct
FILE_PATH = os.path.join(os.path.dirname(__file__), 'demo.mp4')
def socket_client():
client = socket.socket()
client.connect(('127.0.0.1',8848))
# 1.制作字典形式的报头
head_dic = {
'MD5': 123244546656,
'file_name':os.path.basename(FILE_PATH),
'file_size': os.path.getsize(FILE_PATH),
'new_file_name': 'demo1.mp4',
}
# 2. 获取json形式的报头
head_dic_json = json.dumps(head_dic)
# 3. 获取bytes形式的报头
head_dic_json_bytes = head_dic_json.encode('utf-8')
# 4. 获取bytes报头的总字节数
head_len = len(head_dic_json_bytes)
# 5. 将bytes报头的总字节数转化成固定4个字节
four_bytes = struct.pack('i',head_len)
# 6. 发送固定的4个字节
client.send(four_bytes)
# 7.发送报头
client.send(head_dic_json_bytes)
# 8. 发送总数据
'''
循环条件设定:
1. 根据总子节数: file_size: 493701, 每次循环1024个,
2. 每次取数据不为空,即可.
'''
with open(FILE_PATH,mode='rb') as f1:
while 1:
every_data = f1.read(1024)
if every_data:
client.send(every_data)
else:
break
client.close()
socket_client()
11. 基于UDP协议的socket
服务端
import socket
udp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 基于网络,udp协议的socket
udp_server.bind(('127.0.0.1', 9000))
while 1:
from_client_data = udp_server.recvfrom(1024)
print(f'来自{from_client_data[1]}的消息:{from_client_data[0].decode("utf-8")}')
to_client_data = input('>>>').strip()
udp_server.sendto(to_client_data.encode('utf-8'),from_client_data[1])
客户端
import socket
udp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 基于网络,udp协议的socket
while 1:
to_server_data = input('>>>').strip()
udp_client.sendto(to_server_data.encode('utf-8'),('127.0.0.1', 9000))
from_server_data = udp_client.recvfrom(1024)
print(f'来自{from_server_data[1]}的消息:{from_server_data[0].decode("utf-8")}')
12. socketserver
服务端
import socketserver
class MyServer(socketserver.BaseRequestHandler): # 继承的类固定的
def handle(self): # 必须是这个handle名字.
while 1:
from_client_data = self.request.recv(1024).decode('utf-8') # self.request == conn管道
print(from_client_data)
to_client_data = input('>>>').strip()
self.request.send(to_client_data.encode('utf-8'))
if __name__ == '__main__':
ip_port = ('127.0.0.1',8848)
server = socketserver.ThreadingTCPServer(ip_port,MyServer)
# server.allow_reuse_address = True
# print(socketserver.ThreadingTCPServer.mro())
# [ThreadingTCPServer, ThreadingMixIn,TCPServer, BaseServer]
server.serve_forever()
客户端
import socket
# 1. 创建socket对象(买手机)
phone = socket.socket() # 可以默认不写
# 连接服务器ip地址与端口
phone.connect(('127.0.0.1', 8848))
# 发消息
while 1:
content = input('>>>').strip()
phone.send(f'怼哥:{content}'.encode('utf-8'))
# 接收消息
from_server_data = phone.recv(1024) # 夯住,等待服务端的数据传过来
print(f'来自服务端消息:{from_server_data.decode("utf-8")}')
# 关机
phone.close()
13. 进程基础知识
1. 什么是程序?
程序就是一堆文件
2.进程是什么?
进程就是一个正在执行的文件/程序
3.进程被谁执行
cpu最终运行你的程序
操作系统调度作用,将你的磁盘上的程序加载到内存,然后交由cpu处理,一个cpu正在运行的一个程序,就叫开启了一个进程
14. 操作系统
1. 操作系统的定义
操作系统是存在于硬件与软件之间,管理,协调和控制软件与硬件之间的交互
2. 操作系统的作用
1. 将一些丑陋复杂的硬件操作封装成美丽的接口,便于使用
2. 合理的调度分配多个进程与cpu之间的关系,让其有序化
15. 操作系统的发展 ( 多道术 )
第一代电子计算机 1940-1955
第二代计算机 磁带存储,批处理系统 1955-1965
第三代计算机 继承电路多道程序系统
阻塞: IO阻塞(write input read sleep recv, accept join 等等).
如果多个进程全部都没有IO阻塞,多道技术就会影响最终的效率
第三代计算机开始,多道技术结局了提高内存的利用率: 空间上的复用
一个内存可以加载多个进程.
多道技术:
1. 空间上的复用.(内存可以加载多个进程,前期没有实现物理隔离.)
2. 时间上的复用: 操作系统可以调配cpu在不同的进程之间切换.雨露均沾.(遇到IO阻塞就会切换,一个进程的停留时间过长,就会切换.)
16. 进程
串行 : 所有的进程由cpu依次有序解决
并发 : 单个cpu ,同时执行多个进程(来回切换),表面像是同时运行
并行 : 多个cpu,真正的同时运行多个进程
阻塞 : 遇到IO 叫阻塞
什么是开启多个进程 : socket server client 两个进程
python中如果一次想开启多个进程,必须是一个主程序来开启多个子程序
linux,windows 都是由主进程开启子进程
相同点 : 主程序开启子进程,两个进程都存在于相互隔离的独立空间,互不影响
不同点 :
linux : 子进程空间的初识数据完全是由主程序copy的
windows :
开启进程的两种方式
from multiprocessing import Process
import time
def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is done')
if __name__ == '__main__': # windows环境下,开启多进程一定放在这个下面
p = Process(target=task,args=('怼哥',)) # args 一定是一个元组的形式.
p.start()
# 通知操作系统,你给我在内存中开辟一个空间,将p这个进程放进去,然后让cpu执行.
print('===主进程')
# 第二种方式 了解
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self, name):
super().__init__() # 必须要继承父类的__init__
self.name = name
def run(self): # 必须定义run名字.
print(f'{self.name} is running')
time.sleep(3)
print(f'{self.name} is done')
if __name__ == '__main__': # windows环境下,开启多进程一定放在这个下面
p = MyProcess('怼怼哥')
p.start()
# 通知操作系统,你给我在内存中开辟一个空间,将p这个进程放进去,然后让cpu执行.
print('===主进程')
17. pid
在CMD终端中查看系统所有进程的pid
tasklist
查看单个已运行进程的pid
tasklist | findstr pycharm
pycharm64.exe 9224 Console 1 75,756 K
在python中通过代码查看pid
import os
print(f'子进程:{os.getpid}')
18. join
# join 等待,主进程等待子进程结束之后,在执行.
from multiprocessing import Process
import time
def task(name):
print(f'{name} is running')
if __name__ == '__main__':
p = Process(target=task,args=('李业',))
p.start()
time.sleep(1)
print('===主进程')
# 上面的版本,虽然达到目的,但是生产环境中,子进程结束的时间不定. 需要用到join
from multiprocessing import Process
import time
def task(name):
time.sleep(1)
print(f'{name} is running')
if __name__ == '__main__':
p = Process(target=task,args=('李业',))
p.start()
p.join() # 告知主进程,p进程结束之后,主进程在运行.
# join感觉有一些阻塞的意思.
print('===主进程')
# 开启多个子进程去验证:
from multiprocessing import Process
import time
def task(name):
time.sleep(1)
print(f'{name} is running')
if __name__ == '__main__':
p1 = Process(target=task, args=('李业',))
p2 = Process(target=task, args=('怼哥',))
p3 = Process(target=task, args=('mc骚Q',))
p1.start()
p2.start()
p3.start()
# p1, p2, p3 三个子进程运行的先后顺序不定.
# start只是通知一下操作系统,三个start几乎同一时刻发给操作系统,
# 操作系统调用cpu先运行谁,谁先执行.
print('===主进程')
#下面的版本验证了: 如此写join并不是串行.
from multiprocessing import Process
import time
def task(name,sec):
time.sleep(sec)
print(f'{name} is running')
if __name__ == '__main__':
p1 = Process(target=task, args=('李业', 1))
p2 = Process(target=task, args=('怼哥', 2))
p3 = Process(target=task, args=('mc骚Q', 3))
start_time = time.time()
p1.start()
p2.start()
p3.start()
# p1, p2, p3 三个子进程运行的先后顺序不定.
# start只是通知一下操作系统,三个start几乎同一时刻发给操作系统,
# 操作系统调用cpu先运行谁,谁先执行.
p1.join() # 阻塞不在这. time.sleep(1)
p2.join()
p3.join()
print(f'===主进程:{time.time() - start_time}之后,执行') # 三秒
19. 多进程
from multiprocessing import Process
import time
def task(name,sec):
time.sleep(sec)
print(f'{name} is running')
if __name__ == '__main__':
p1 = Process(target=task, args=('李业', 1))
p2 = Process(target=task, args=('怼哥', 2))
p3 = Process(target=task, args=('mc骚Q', 3))
start_time = time.time()
p1.start()
p1.join()
p2.start()
p2.join()
p3.start()
p3.join()
print(f'===主进程:{time.time() - start_time}之后,执行') # 6秒
from multiprocessing import Process
import time
def task(sec):
time.sleep(sec)
print(f'{sec}: is running')
if __name__ == '__main__':
start_time = time.time()
p_l = []
for i in range(1,4):
p = Process(target=task, args=(i,))
p.start()
p_l.append(p)
for i in p_l:
i.join()
print(f'===主进程:{time.time() - start_time}之后,执行') #三秒
20. 僵尸/孤儿进程
僵尸进程 :
当子进程比父进程先结束,并且把他(子进程)的所有资源和信息全部释放掉,父进程就无法获得子进程结束时的状态信息.那么此时子进程将成为一个僵尸进程.
孤儿进程 :
在其父进程执行完成或被终止后仍继续运行的子进程,这些孤儿进程将被init进程(进程号位1)所收养,并由init进程对它们完成状态收集工作.
僵尸进程和孤儿进程哪个无害 :
孤儿进程无害,如果僵尸进程挂掉了,init会对孤儿进程进行回收
僵尸进程有害:
僵尸进程无限的开启子进程,递归的开启,子进程越来越多,僵尸进程还没有结束,会导致进程越来越多,占内存
21. 守护进程
守护进程 :
1. 当子进程被设置为守护进程后,如果主进程结束了,那么无论这个子进程是否结束,都会立即结束
2.守护进程不能有子进程
from multiprocessing import Process
import time
import os
def task(name):
print(f'{name} is running')
print(f'子进程开始了:{os.getpid()}')
time.sleep(50)
if __name__ == '__main__':
p = Process(target=task,args=('怼哥',))
p.daemon = True # 将p子进程设置成守护进程,守护主进程,只要主进程结束,子进程无论执行与否,都马上结束.
p.start()
time.sleep(2)
print(f'主进程开始了:{os.getpid()}')
22. 互斥锁
互斥锁 进程锁 同步锁 线程锁 都是同一把所 Lock
# 业务: 三台电脑同一时刻共同调用打印机,完成打印业务.
# 3个进程,同一时刻共抢一个资源: 输出平台.
# from multiprocessing import Process
# import time
# import random
# def task1():
# print('task1: 开始打印')
# time.sleep(random.randint(1,3))
# print('task1: 打印完成')
#
# def task2():
# print('task2: 开始打印')
# time.sleep(random.randint(1, 3))
# print('task2: 打印完成')
#
# def task3():
# print('task3: 开始打印')
# time.sleep(random.randint(1, 3))
# print('task3: 打印完成')
#
# if __name__ == '__main__':
# p1 = Process(target=task1,)
# p2 = Process(target=task2,)
# p3 = Process(target=task3,)
#
# p1.start()
# p2.start()
# p3.start()
# 多个进程共抢一个资源,你要是做到结果第一位,效率第二位.
# 你应该牺牲效率,保求结果.串行.
# 版本二:
# from multiprocessing import Process
# import time
# import random
# def task1():
# print('task1: 开始打印')
# time.sleep(random.randint(1,3))
# print('task1: 打印完成')
#
# def task2():
# print('task2: 开始打印')
# time.sleep(random.randint(1, 3))
# print('task2: 打印完成')
#
# def task3():
# print('task3: 开始打印')
# time.sleep(random.randint(1, 3))
# print('task3: 打印完成')
#
# if __name__ == '__main__':
# p1 = Process(target=task1,)
# p2 = Process(target=task2,)
# p3 = Process(target=task3,)
#
# p2.start()
# p2.join()
# p1.start()
# p1.join()
# p3.start()
# p3.join()
# 虽然说上面版本完成了串行结果,保证了顺序,但是没有实现公平.
# 顺序是人为写好的.我们要做到他公平去抢占打印机资源,谁先抢到,先执行谁.
# 三个人共住, 谁先起来谁先上厕所,你们抢的是厕所的那把锁,抢到锁锁上,你就上厕所.完事儿之后,打开锁.
# 版本三:
from multiprocessing import Process
from multiprocessing import Lock
import time
import random
def task1(lock):
print('task1') # 验证cpu遇到io切换了
lock.acquire()
print('task1: 开始打印')
time.sleep(random.randint(1, 3))
print('task1: 打印完成')
lock.release()
def task2(lock):
print('task2') # 验证cpu遇到io切换了
lock.acquire()
print('task2: 开始打印')
time.sleep(random.randint(1, 3))
print('task2: 打印完成')
lock.release()
def task3(lock):
print('task3') # 验证cpu遇到io切换了
lock.acquire()
print('task3: 开始打印')
time.sleep(random.randint(1, 3))
print('task3: 打印完成')
lock.release()
if __name__ == '__main__':
lock = Lock()
p1 = Process(target=task1, args=(lock,))
p2 = Process(target=task2, args=(lock,))
p3 = Process(target=task3, args=(lock,))
p1.start()
p2.start()
p3.start()
# 上锁:
# 一定要是同一把锁: 只能按照这个规律:上锁一次,解锁一次.
# 互斥锁与join区别共同点? (面试题)
# 共同点: 都是完成了进程之间的串行.
# 区别: join认为控制的进程串行,互斥锁是随机的抢占资源.保证了公平性
# 业务需求分析:
# 买票之前先要查票,必须经历的流程: 你在查票的同时,100个人也在查本此列票.
# 买票时,你要先从服务端获取到票数,票数>0 ,买票,然后服务端票数减一. 中间肯定有网络延迟.
from multiprocessing import Process
from multiprocessing import Lock
import time
import json
import os
import random
# 多进程原则上是不能互相通信的,它们在内存级别数据隔离的.不代表磁盘上数据隔离.
# 它们可以共同操作一个文件.
def search():
time.sleep(random.random())
with open('db.json',encoding='utf-8') as f1:
dic = json.load(f1)
print(f'剩余票数{dic["count"]}')
def get():
with open('db.json',encoding='utf-8') as f1:
dic = json.load(f1)
time.sleep(random.randint(1,3))
if dic['count'] > 0:
dic['count'] -= 1
with open('db.json', encoding='utf-8', mode='w') as f1:
json.dump(dic,f1)
print(f'{os.getpid()}用户购买成功')
else:
print('没票了.....')
def task(lock):
search()
lock.acquire()
get()
lock.release()
if __name__ == '__main__':
lock = Lock()
for i in range(5):
p = Process(target=task,args=(lock,))
p.start()
# with open('db.json', encoding='utf-8',mode='w') as f1:
# json.dump({'count': 3},f1)
23. 进程之间的通信 : 队列
队列是存在于内存中的一个容器,最大的特点是FIFO(先进先出)完全遵循先进先出的原则
进程之间的通信最好的方式是 : 队列
1. maxsize() q = Queue(3) 数据量不易过大.精简的重要的数据.
2. put block 默认为True 当你插入的数据超过最大限度,默认阻塞.
q = Queue(3) # 可以设置元素个数
q.put('alex')
q.put({'count': 1})
q.put(22) #
q.put(333,block=False) # 改成False 数据超过最大限度,不阻塞了直接报错.
3. put timeout() 参数
q = Queue(3) # 可以设置元素个数
from multiprocessing import Queue
q.put('alex')
q.put({'count': 1})
q.put(22) #
q.put(333,timeout=3) # 延时报错,超过三秒再put不进数据,就会报错.
q = Queue()
q.put('alex')
q.put({'count': 1})
q.put(22) #
print(q.get())
print(q.get())
print(q.get())
print(q.get(block=False)) timeout
24. 进程之间的通信实例
利用队列进行进程之间通信: 简单,方便,不用自己手动加锁.队列自带阻塞,可持续化取数据
# 小米:抢手环4.预期发售10个.
# 有100个人去抢.
import os
from multiprocessing import Queue
from multiprocessing import Process
def task(q):
try:
q.put(f'{os.getpid()}',block=False)
except Exception:
return
if __name__ == '__main__':
q = Queue(10)
for i in range(100):
p = Process(target=task,args=(q,))
p.start()
for i in range(1,11):
print(f'排名第{i}的用户: {q.get()}',)
25. 生产者消费者模型
模型, 设计模式,归一化设计, 理论等等,教给你一个编程思路.如果以后遇到类似的情况,直接套用即可.
生产者: 生产数据进程.
消费者: 对生产者生产出来的数据做进一步处理进程.
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯。所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这个阻塞队列就是用来给生产者和消费者解耦的
如果没有容器, 生产者与消费者强耦合性.不合理.所以我们要有一个容器,缓冲区.平衡了生产力与消费力.
生产者消费者多应用于并发.
from multiprocessing import Process
from multiprocessing import Queue
import time
import random
def producer(name,q): # 生产者
for i in range(1,6):
time.sleep(random.randint(1,3))
res = f'{i}号包子'
q.put(res)
print(f'\033[0;32m 生产者{name}: 生产了{res}\033[0m')
def consumer(name,q): # 消费者
while 1:
try:
time.sleep(random.randint(1,3))
ret = q.get(timeout=5)
print(f'消费者{name}: 吃了{ret}')
except Exception:
return
if __name__ == '__main__':
q = Queue()
p1 = Process(target=producer, args=('太白',q))
p2 = Process(target=consumer, args=('MC骚强',q))
p1.start()
p2.start()
一张票五人抢购
from multiprocessing import Process
from multiprocessing import Queue
import os
import random
import time
def search(ticket):
time.sleep(random.random())
print(f'剩余票数{ticket}')
def _get(q):
dic = q.get()
time.sleep(random.randint(1,3))
if dic["count"] > 0:
dic["count"] -= 1
print(f'{os.getpid()用户购票成功}')
else:
print(f"用户{os.getpid()没票了}")
q.put(dic)
def task(ticket,q):
search(ticket)
_get(q)
if __name__ == '__main__':
q = Queue(1)
q.put({"count":1})
ticket = 1
for i in range(5):
p = Process(target=task,args=ticket,q)
p.start()
生产者消费者模型:
合理的去调控多个进程去生成数据以及提取数据,中间有个必不可少的环节容器队列.
26. 线程的理论知识
什么是进程
将执行文件(代码,数据)加载到内存中,等待cpu运算, 是相对静态的一个过程
什么是线程
执行代码,运算的一个过程 ,是动态的一个过程
线程是依赖于进程的,一个进程可以包含多个线程,但是一定有一个主线程 ,
线程是操作系统能够进行运算调度的最小单位
线程VS进程: 1.开启多进程开销大 开启多线程开销非常小 2.开启多进程速度慢, 开启多线程速度快 3.进程之间数据不能直接共享, 通过队列可以共享磁盘数据 ; 同一进程下的线程之间数据可以共享
多线程应用场景 并发 : 一个cpu在线程之间来回切换,多进程并发,多线程并发 多进程并发 : 开启多个进程, 每个进程里面的主线程执行任务 多线程并发 : 开启1个进程,此进程里面多个线程执行任务
27. 开启线程的两种方式
# 第一种方式
from threading import Thread
def task(name):
print(f'{name} is running')
if __name__ == '__main__':
t = Thread(target=task,args=("子线程",))
t.start()
print('主线程')
"""
子线程 is running
主线程
"""
# 第二种方式
from threading import Thread
class MyThread(Thread):
def run(self):
print(f'{self.name} is running')
if __name__ == '__main__':
t = MyThread()
t.start()
print("主线程")
"""
Thread-1 is running
主线程
"""
28. 线程与进程的对比
3.1 pid
from threading import Thread
import os
def task():
print(f'子进程 : {os.getpid()}')
if __name__ == '__main__':
t = Thread(target=task)
t.start()
print(f'主进程 : {os.getpid()}')
"""
子进程 : 6848
主进程 : 6848
"""
# 线程从属于进程
3.2 线程之间数据互通
from threading import Thread
x = 100
def task():
global x
x = 0
if __name__ == '__main__':
t = Thread(target=task)
t.start()
t.join()
print(x) # 0
from threading import Thread
import time
def task():
time.sleep(3)
print("子进程...")
def main():
print(111)
print(222)
if __name__ == '__main__':
t = Thread(target=task)
t.start()
t.join()
main()
"""
子进程...
111
222
"""
29. 线程的其他方法
from threading import Thread
import time
import threading
def task(name):
time.sleep(1)
# print(f'{name} is running ')
print(threading.current_thread().name) # 线程名
if __name__ == '__main__':
for i in range(5):
t = Thread(target=task,args=('python',),name="win10")
t.start()
print(t.getName()) # 获取线程名
t.setName("新的线程名") # 设置线程名
print(t.is_alive()) # 判断子线程是否存活
# threading 模块的几个用法
print(threading.current_thread().name) # MainThread -这里是主线程
print(threading.enumerate()) # 返回的是所有线程的列表对象
print(threading.active_count()) # 获取活跃的线程的数量,包括主线程
print('主线程')
30. 守护线程
# 守护线程
from threading import Thread
import time
def foo():
print("foo_123")
time.sleep(5)
print('end_foo')
def bar():
print("bar_456")
time.sleep(1)
print("end_bar")
if __name__ == '__main__':
t1 = Thread(target=foo)
t2 = Thread(target=bar)
t1.daemon = True
t1.start()
t2.start()
print('主进程')
"""
foo_123
bar_456
主进程
end_bar
"""
# 主线程 : 在所有非守护线程结束之后才结束
# 守护进程 : 在主进程结束之后立即结束
31. 互斥锁
from threading import Thread
import time
x = 100
def task():
global x
temp = x
time.sleep(1)
temp -= 1
x = temp
if __name__ == '__main__':
t = Thread(target=task)
t.start()
t.join()
print(f'主线程{x}') # 主线程99
# 开启100个线程
from threading import Thread
import time
x = 100
def task():
global x
temp = x
time.sleep(1)
temp -= 1
x = temp
if __name__ == '__main__':
t_l = []
for i in range(100):
t = Thread(target=task)
t_l.append(t)
t.start()
for i in t_l:
i.join()
print(f'主线程{x}')
# 主线程99
'''
第一个线程: x = 100 剩下的线程拿到的也是 x
第一个线程执行完毕: x = 99
第二个线程进去:temp = 99
'''
# 睡1秒之前,所有的线程已经拿到了temp = 100,一秒过后,都是从x = 100来修改的
# 加锁
from threading import Thread
from threading import Lock
import time
x = 100
def task(lock):
global x
lock.acquire()
temp = x
time.sleep(1)
temp -= 1
x = temp
lock.release()
if __name__ == '__main__':
lock = Lock()
t_l = []
for i in range(100):
t = Thread(target=task,args=(lock,))
t_l.append(t)
t.start()
for i in t_l:
i.join()
print(f'主线程{x}')
# 主线程0
# 互斥锁与join区别?
# 互斥锁 随机抢锁,公平. join 提前排好顺序,不公平.但是都是串行.
32. 死锁现象 - 解决办法-递归锁
from threading import Thread
from threading import Lock
import time
lock_A = Lock()
lock_B = Lock()
class MyThread(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
lock_A.acquire()
print(f'{self.name}拿到 A锁')
lock_B.acquire() # 第二个线程抢B锁
print(f'{self.name}拿到 B锁')
lock_B.release()
lock_A.release()
def f2(self):
lock_B.acquire()
print(f'{self.name}拿到 B锁')
time.sleep(1) # 第一个线程 睡1秒
lock_A.acquire() # 第一个线程抢A锁
print(f'{self.name}拿到 A锁')
lock_A.release()
lock_B.release()
if __name__ == '__main__':
# for i in range(3):
# t = MyThread()
# t.start()
t1 = MyThread()
t1.start()
t2 = MyThread()
t2.start()
t3 = MyThread()
t3.start()
print('主线程')
"""
Thread-1拿到 A锁
Thread-1拿到 B锁
Thread-1拿到 B锁
Thread-2拿到 A锁
主线程
程序夯住
"""
# 递归锁
# 递归锁是一把锁,锁上有记录,只要acquire一次,锁上就计数1次, acquire2次,锁上就计数2次,
# release1次,减一,
# 只要递归锁计数不为0,其他线程不能抢.
from threading import Thread
from threading import Lock
from threading import RLock
import time
# lock_A = RLock()
# lock_B = RLock()
lock_A = lock_B = RLock()
class MyThread(Thread):
def run(self):
self.f1()
self.f2()
def f1(self):
lock_A.acquire()
print(f'{self.name}拿到 A锁')
lock_B.acquire() # 第二个线程抢B锁
print(f'{self.name}拿到 B锁')
lock_B.release()
lock_A.release()
def f2(self):
lock_B.acquire()
print(f'{self.name}拿到 B锁')
time.sleep(1) # 第一个线程 睡1秒
lock_A.acquire() # 第一个线程抢A锁
print(f'{self.name}拿到 A锁')
lock_A.release()
lock_B.release()
if __name__ == '__main__':
# for i in range(3):
# t = MyThread()
# t.start()
t1 = MyThread()
t1.start()
t2 = MyThread()
t2.start()
t3 = MyThread()
t3.start()
print('主线程')
"""
Thread-1拿到 A锁
Thread-1拿到 B锁
Thread-1拿到 B锁
主线程
Thread-1拿到 A锁
Thread-2拿到 A锁
Thread-2拿到 B锁
Thread-2拿到 B锁
Thread-2拿到 A锁
Thread-3拿到 A锁
Thread-3拿到 B锁
Thread-3拿到 B锁
Thread-3拿到 A锁
"""
33. 信号量
# 之前讲的锁都是只允许一个线程或者进程进入.
# 信号量允许多个线程或者进程同时进入
# 信号量相当于队列,主要有一个锁空闲,就要进一个线程
from threading import Thread
from threading import current_thread
from threading import Semaphore
import time
import random
sm = Semaphore(4) # 设置锁的个数
# lock= Lock()
def go_public_wc():
sm.acquire()
print(f'{current_thread().name}正在厕所')
time.sleep(random.randint(1, 3))
sm.release()
if __name__ == '__main__':
for i in range(20):
t = Thread(target=go_public_wc)
t.start()
# print('主')
34. GIL锁
# GIL锁 :
全局解释器锁 ,本身就是一把互斥锁,将并发变成串行,同一时刻,只能有一个线程去使用共享资源,牺牲了效率,保证了数据的安全
# 优点 :
保证解释器数据的安全
减轻开发人员的负担
# 缺点:
单进程的多线程不能利用多核
但是多进程的线程可以利用多核
单核处理IO阻塞的多线程 , 与多核处理IO阻塞的多线程效率差不太多
# 多核的前提下:
如果任务是IO密集型 : 使用多线程并发
如果任务是计算密集型 : 多进程并发
35. 验证Cpython的多进程和多线程的并发效率
# 结论 :
计算密集型 : 多进程的并行执行效率高
IO密集型 : 并且任务数量很大,单进程下的多线程效率高
多进程和多线程的选择
# 在多核的前提下,计算密集型程序喝IO密集型程序选择多线程还是多进程 :
1.在计算密集型程序中,由于程序中多计算,若使用多线程,多线程无法使用多核多线程并行,因此无法提升效率,若选择多进程,多进程可以利用多核进行多进程并行,大大提高了运算速度,提高效率
2.在IO秘籍程序中,由于程序中多为IO堵塞,若使用多进程,首先多进程开启需要消耗较多内存,开销大,且开启进程和cpu在进程之间切换较慢,,因此效率较慢.若使用多线程,一个进程开启多个线程是相当快的,并且cpu在线程之前的切换也较进程快,因此在IO密集程序中,选择多线程
36. GIL锁与互斥锁的关系
互斥锁一定要加在处理共享数据的地方
# 互斥锁 :
进程之间的数据不能共享,但是共享同一套文件系统,所以访问同一个文件,或者同一个打印终端,是没有问题的,而共享带来的是竞争竞争带来是错乱,由此引来了互斥锁,互斥锁的作用就是要想使用共享文件,必须要先抢这个锁,先抢到的可以先使用,其他人排队,等到解锁后后再次抢锁
37. 进程池线程池
进程池/线程池:
放置进程/线程的容器
系统启动一个进程/线程的成本还是比较高的,(开启线程涉及与操作系统的交互),当程序中需要创建大量生存期很短暂的线程时,就需要考虑到进程池/线程池,他可以很好的提高性能
线程池在系统启动时会创建大量空闲进程/线程,程序只需要把任务提交给线程池,线程池就会启动一个空闲的线程执行它,当任务完成后,该线程不会死亡,会再次回到线程池中处于空闲状态,等待任务.
进程/线程池的最大线程数可以控制系统中并发线程数量,从而使系统不过载
使用线程池来执行线程任务的步骤如下:
1. 调用 ThreadPoolExecutor 类的构造器创建一个线程池。
2. 定义一个普通函数作为线程任务。
3. 调用 ThreadPoolExecutor 对象的 submit() 方法来提交线程任务。
4. 当不想提交任何任务时,调用 ThreadPoolExecutor 对象的 shutdown() 方法来关闭线程池。
线程池开销很小,但是不宜无限制开启,以满足计算机性能的最大需求为宜
进程/线程池的开启,节省了系统执行程序时,从新开启进程/线程池的时间,提高了效率,
什么时候用池:
池的功能是限制启动的进程/线程数的
什么时候应该限制 :
当并发的任务远远超过了计算机的承受能力时,即无法一次性开启过多的进程/线程数时就应该用池的概念将开启的进程/线程数限制在计算机可承受的范围内
池中 p.shutdown(wait=True)是进程池内部的进程都执行完毕,才会关闭,然后执行后续代码
# 进程池
from concurrent.futures import ProcessPoolExecutor
import time
import os
import random
def task(name):
print(name)
print(f'{os.getpid()} 准备接客')
time.sleep(random.randint(1,3))
if __name__ == '__main__':
p = ProcessPoolExecutor() # 设置进程数量默认为cpu个数
for i in range(23):
p.submit(task,1) # 给进程池放任务,传参1
# 线程池
from concurrent.futures import ThreadPoolExecutor
import time
import os
import random
def task(name):
print(name)
print(f'{os.getpid()} 准备接客')
time.sleep(random.randint(1,3))
if __name__ == '__main__':
p = ThreadPoolExecutor() # ,默认cpu数量*5
for i in range(23):
p.submit(task,1) # 给线程池放任务,传参1
简单的socket通信, 服务端必须与一个客户端交流完毕并且这个客户端断开连接之后,服务端才能接待下一个客户端.....
# 服务端
import socket
from threading import Thread
def communication(conn):
while 1:
try:
from_client_data = conn.recv(1024) # 阻塞
print(from_client_data.decode('utf-8'))
to_client_data = input('>>>').strip()
conn.send(to_client_data.encode('utf-8'))
except Exception:
break
conn.close()
def customer_service():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()
while 1:
conn,addr = server.accept() # 阻塞
print(f'{addr}客户:')
t = Thread(target=communication,args=(conn,))
t.start()
server.close()
if __name__ == '__main__':
customer_service()
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
while 1:
to_server_data = input('>>>').strip()
client.send(to_server_data.encode('utf-8'))
from_server_data = client.recv(1024)
print(f'客服回信: {from_server_data.decode("utf-8")}')
client.close()
38. 阻塞 非阻塞 同步 异步
阻塞 :
程序运行时表现的状态,程序遇到IO立马会挂起,cpu马上切换,等到IO结束后,切换回来运行
非阻塞 :
程序运行,就绪,程序中没有IO,或者遇到IO,通过某种手段(协程),让cpu执行其他任务,尽可能的占用cpu
异步/同步 : 站在任务发布的角度考虑
同步 :
任务发出之后,如果遇到IO,等待IO,,直到这个任务最终结束,返回一个结果后,再发布下一个任务(串行)
异步 :
把任务全部分发出去,等待返回值
shutdown(wait = True) 阻止向进程池投放新任务, wait = True一个任务完成后-1,直至为零 然后执行下一行
# 异步 :
from concurrent.futures import ProcessPoolExecutor
import os
import time
import random
def task():
print(f'{os.getpid()} is running')
time.sleep(random.randint(0,2))
return f'{os.getpid()} is finish'
if __name__ == '__main__':
p = ProcessPoolExecutor(4)
obj_l1 = []
for i in range(10):
obj = p.submit(task,) # 异步发出.
obj_l1.append(obj)
# time.sleep(3)
p.shutdown(wait=True)
# 对shutdown的解释 :
# 1. 阻止在向进程池投放新任务,
# 2. wait = True 十个任务是10,一个任务完成了-1,直至为零.进行下一行.
# print(666)
for i in obj_l1:
print(i.result())
# 异步回收任务的方式一: 我将所有的任务的结果统一收回.
# 同步发布任务: 我要发布10个任务,先把第一个任务给第一个进程,等到第一个进程完成之后.
# 我在将第二任务给了下一个进程,......
# 异步发布任务: 我直接将10个任务抛给4个进程, 我就继续执行下一行代码了.等结果.
# 同步 :
from concurrent.futures import ProcessPoolExecutor
import os
import time
def task():
print(f'{os.getpid()} is running')
time.sleep(1)
return f'{os.getpid()} is finish'
if __name__ == '__main__':
p = ProcessPoolExecutor(4)
for i in range(10):
obj = p.submit(task,) # 异步发出.
print(obj.result())
39. 异步 + 调用机制
爬虫1.1
# 爬虫
# 第一步: 爬取服务端的文件(IO阻塞).
# 第二步: 拿到文件,进行数据分析,(非IO,IO极少)
#
import requests
from concurrent.futures import ProcessPoolExecutor
from multiprocessing import Process
import time
import random
import os
def get(url):
response = requests.get(url)
print(f'{os.getpid()} 正在爬取:{url}')
time.sleep(random.randint(1,3))
if response.status_code == 200:
return response.text
def parse(text): # 数据分析
print(f'{os.getpid()} 分析结果:{len(text)}')
if __name__ == '__main__':
url_list = [
'http://www.taobao.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.baidu.com',
'https://www.cnblogs.com/jin-xin/articles/11232151.html',
'https://www.cnblogs.com/jin-xin/articles/10078845.html',
'http://www.sina.com.cn',
'https://www.sohu.com',
'https://www.youku.com',
]
pool = ProcessPoolExecutor(4)
obj_list = []
for url in url_list:
obj = pool.submit(get, url) # 发送任务
obj_list.append(obj) # 将对象动态放入列表中
pool.shutdown(wait=True) # 相当于join
for obj in obj_list:
parse(obj.result())
## 从获取网页数据到分析数据整个过程是串行的,效率低
爬虫1.2
import requests
from concurrent.futures import ProcessPoolExecutor
from multiprocessing import Process
import time
import random
import os
def get(url):
response = requests.get(url)
print(f'{os.getpid()} 正在爬取:{url}')
time.sleep(random.randint(1,3))
if response.status_code == 200:
parse(response.text)
def parse(text):
print(f'{os.getpid()} 分析结果:{len(text)}')
if __name__ == '__main__':
url_list = [
'http://www.taobao.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.baidu.com',
'https://www.cnblogs.com/jin-xin/articles/11232151.html',
'https://www.cnblogs.com/jin-xin/articles/10078845.html',
'http://www.sina.com.cn',
'https://www.sohu.com',
'https://www.youku.com',
]
pool = ProcessPoolExecutor(4)
for url in url_list:
obj = pool.submit(get, url)
# pool.shutdown(wait=True)
print('主')
## 每获取一个网页数据就执行分析数据函数,返回结果,并发进行,但是用强耦性,
爬虫1.3 obj.add_done_callback(函数名)
回调函数
对1.2进行解耦
!! 若回调函数是IO任务,由于回调函数是主进程做的,会降低效率
解决办法,把分析函数写到新的进程里
import requests
from concurrent.futures import ProcessPoolExecutor
from multiprocessing import Process
import time
import random
import os
def get(url):
response = requests.get(url)
print(f'{os.getpid()} 正在爬取:{url}')
# time.sleep(random.randint(1,3))
if response.status_code == 200:
return response.text
def parse(obj):
time.sleep(1)
print(f'{os.getpid()} 分析结果:{len(obj.result())}')
if __name__ == '__main__':
url_list = [
'http://www.taobao.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.baidu.com',
'https://www.cnblogs.com/jin-xin/articles/11232151.html',
'https://www.cnblogs.com/jin-xin/articles/10078845.html',
'http://www.sina.com.cn',
'https://www.sohu.com',
'https://www.youku.com',
]
start_time = time.time()
pool = ProcessPoolExecutor(4)
for url in url_list:
obj = pool.submit(get, url)
obj.add_done_callback(parse) # 增加一个回调函数
# 现在的进程完成的还是网络爬取的任务,拿到了返回值之后,结果丢给回调函数add_done_callback,
# 回调函数帮助你分析结果
# 进程继续完成下一个任务.
pool.shutdown(wait=True)
print(f'主: {time.time() - start_time}')
40. 线程队列
1. FIFO Queue 先进先出 :
q = Queue.Queue(3)
2. LIFO 堆栈 后进先出 :
import Queue
q = queue.LifoQueue(3)
3. 优先级队列
import queue
q = queue.PriorityQueue(3)
q.put((10,"垃圾消息")) # 放元组 (级别,内容)
q.put((-10,"紧急消息"))
# 1 FIFO queue
import queue
q = queue.Queue(3)
q.put(1)
q.put(2)
q.put('太白')
# q.put(666) # put多余设置的最大数同样会夯住
print(q.get())
print(q.get())
print(q.get())
"""
1
2
太白
"""
# 2 LIFO 栈.
import queue
q = queue.LifoQueue()
q.put(1)
q.put(3)
q.put('barry')
print(q.get())
print(q.get())
print(q.get())
print(q.get())
"""
barry
3
1
夯住
"""
# 3.优先级队列
# 需要元组的形式,(int,数据) int 代表优先级,数字越低,优先级越高.
import queue
q = queue.PriorityQueue(3)
q.put((10, '垃圾消息'))
q.put((-9, '紧急消息'))
q.put((3, '一般消息'))
print(q.get())
print(q.get())
print(q.get())
41. 事件 Event 进程/线程
每个进程/线程的运行状态是不可测的,如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。
对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
from threading import Event
even = Event() # event 默认是False
event.set() # 修改为True
event.wait() # 轮询检测event是否为True,若为True,则执行下一行代码(阻塞)
event.wait(2) # 设置超时时间,若在设置时间内event为True或超出时间后都会执行下一行代码
event.isSet() # 返回event的状态值;
event.clear() # 恢复event的状态值为False。
不用Event 一个进程在某一节点,让另一个进程执行
import time
from threading import Thread
from threading import current_thread
flag = False
def task():
print(f'{current_thread().name} 检测服务器是否正常开启....')
time.sleep(3)
global flag
flag = True
def task1():
while 1:
time.sleep(1)
print(f'{current_thread().name} 正在尝试连接服务器.....')
if flag:
print('连接成功')
return
if __name__ == '__main__':
t1 = Thread(target=task1,)
t2 = Thread(target=task1,)
t3 = Thread(target=task1,)
t = Thread(target=task)
t.start()
t1.start()
t2.start()
t3.start()
import time
from threading import Thread
from threading import current_thread
from threading import Event
event = Event() # 默认是False
def task():
print(f'{current_thread().name} 检测服务器是否正常开启....')
time.sleep(3)
event.set() # 改成了True
def task1():
print(f'{current_thread().name} 正在尝试连接服务器')
# event.wait() # 轮询检测event是否为True,当其为True,继续下一行代码. 阻塞.
event.wait(1)
# 设置超时时间,如果1s中以内,event改成True,代码继续执行.
# 设置超时时间,如果超过1s中,event没做改变,代码继续执行.
print(f'{current_thread().name} 连接成功')
if __name__ == '__main__':
t1 = Thread(target=task1,)
t2 = Thread(target=task1,)
t3 = Thread(target=task1,)
t = Thread(target=task)
t.start()
t1.start()
t2.start()
t3.start()
42. 协程
串行 : 多个任务依次执行,遇到IO阻塞,等待阻塞,阻塞结束后继续执行 ,
并行 : 多核多个进程/线程同时执行
并发 : 表面看来是多个任务同时执行,其实是cpu在多个任务之间快速来回切换
并发的本质 :
1.遇到IO阻塞或是计算密集型执行时间过长时切换
2.切换后的保持切换之前的状态
一个线程实现并发:
多进程: 操作系统控制 多个进程的多个任务切换 + 保持状态.
多线程: 操作系统控制 多个线程的多个任务切换 + 保持状态.
协程: 程序控制 一个线程的多个任务的切换以及保持状态.
协程: 微并发, 处理任务不宜过多.
协程它会调度cpu,如果协程管控的任务中,遇到阻塞,它会快速的(比操作系统快)切换到另一个任务,并且能将
上一个任务挂起(保持状态,),让操作系统以为cpu一直在工作.
协程 - 单线程下的并发
协程:
是单线程下的并发,又称微线程,纤程。英文名Coroutine。
一句话说明什么是线程:
协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。
优点 :
1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
2. 单线程内就可以实现并发的效果,最大限度地利用cpu
3. 修改共享数据不需加锁
缺点 :
1. 协程的本质是单线程,因此无法利用多核,但可以一个进程下开多个协程
2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
多线程并发 : 一个进程下开多线程处理的任务相对较少
多协程并发 : 一个进程下开多协程处理的任务相对较多
import time
def func1():
for i in range(11):
yield
print('这是我第%s次打印啦' % i)
time.sleep(1)
def func2():
g = func1()
#next(g)
for k in range(10):
print('哈哈,我第%s次打印了' % k)
time.sleep(1)
next(g)
func1()
func2()
# 不写yield,上面两个任务是执行完func1里面所有的程序才会执行func2里面的程序,
# 有了yield,我们实现了两个任务的切换+保存状态
计算密集型:串行与协程的效率对比 串行效率高
import time
def task1():
res = 1
for i in range(1,100000):
res += i
def task2():
res = 1
for i in range(1,100000):
res -= i
start_time = time.time()
task1()
task2()
print(f'串行消耗时间:{time.time()-start_time}') # 串行消耗时间:0.012000560760498047
import time
def task1():
res = 1
for i in range(1, 100000):
res += i
yield res
def task2():
g = task1()
res = 1
for i in range(1, 100000):
res -= i
next(g)
start_time = time.time()
task2()
print(f'协程消耗时间:{time.time() - start_time}') # 协程消耗时间:0.0260012149810791
"""
第一种情况的切换。在任务一遇到io情况下,切到任务二去执行,这样就可以利用任务一阻塞的时间完成任务二的计算,效率的提升就在于此。
但是yield不能自动切换,遇到IO不切换 ,可以保持原来的状态
"""
43. Greenlet模块
协程的底层基于Greenlet实现
#真正的协程模块就是使用greenlet完成的切换
# 不能自动切换 必须手动切换(swith)
# 遇到IO还是会堵塞
# 可以保持原来的状态(挂起)
from greenlet import greenlet
def eat(name):
print('%s eat 1' %name) #2
g2.switch('taibai') #3
print('%s eat 2' %name) #6
g2.switch() #7
def play(name):
print('%s play 1' %name) #4
g1.switch() #5
print('%s play 2' %name) #8
g1=greenlet(eat)
g2=greenlet(play)
g1.switch('taibai')#可以在第一次switch时传入参数,以后都不需要 #1
44. Gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
还没有做到真正遇到IO切换
import gevent
import time
def eat(name):
print('%s eat 1' %name) # 1
# gevent.sleep(2) # gevent.sleep() 会切
time.sleep(300) # 自己设置的time.sleep 不会切
print('%s eat 2' %name)
def play(name):
print('%s play 1' %name) # 2
# gevent.sleep(1)
time.sleep(3)
print('%s play 2' %name)
g1 = gevent.spawn(eat, 'alex')
g2 = gevent.spawn(play, name='taibai')
# g1.join()
# g2.join()
#或者gevent.joinall([g1,g2])
gevent.joinall([g1,g2])
print('主')
# 真正的遇到IO会立马切
# 协程应用
import threading
from gevent import monkey
monkey.patch_all() # 将你代码中的所有的IO堵塞都标识.
import gevent # 直接导入即可
import time
def eat():
print(f'线程1:{threading.current_thread().getName()}')
print('eat food 1')
time.sleep(3) # 加上mokey就能够识别到time模块的sleep了
print('eat food 2')
def play():
print(f'线程2:{threading.current_thread().getName()}')
print('play 1')
time.sleep(1) # 来回切换,直到一个I/O的时间结束,这里都是gevent做的,不再是控制不了的操作系统了。
print('play 2')
g1=gevent.spawn(eat)
g2=gevent.spawn(play)
gevent.joinall([g1,g2])
print(f'主:{threading.current_thread().getName()}') # Dummy(假的) 一个主线程来回切换任务在做事情
"""
线程1:DummyThread-1
eat food 1
线程2:DummyThread-2
play 1
play 2
eat food 2
主:MainThread
"""