昨日内容回顾
socket基本使用
# 内置的模块
import socket
s = socket.socket() # 默认是TCP协议 也可以切换为UDP协议
s.bind((ip,port))
s.listen(5)
sock,addr = s.accept()
sock.recv(1204)
sock.send(b'hello')
c = socket.socket()
c.connect((ip,port))
...
通信循环
将recv和send代码区加上while循环即可
链接循环
将监听代码区加上while循环即可
代码健壮性校验
1.异常捕获
2.端口冲突
3.系统问题
在客户端判断用户输入是否为空 continue
在服务端判断接收的消息是否为空 break
TCP黏包特性
1.双向通道中数据量较大有残余
2.TCP会将数据量较小并且时间间隔较短的数据一次性打包发送(流式协议)
如何解决黏包问题
制作报头:固定长度 内部含有诸多信息
能够固定打包数据的模块struct模块
客户端
1.打包固定长度的字典的报头并发送
2.发送字典数据
3.发送真实数据
服务端
1.先接收固定长度的报头 4
2.解析报头获取字典长度 255
3.接收字典数据并解析数据从中获取真实数据相关的信息
4.接收真实数据
大文件传输
1.如何校验文件数据的一致性
使用hashlib模块对文件内容做加密处理比对处理之后的随机字符串(耗时)
切片加密比对
读取文件部分内容加密(比如划分十块区域)
2.数据存储
for循环一行行发送并一行行存储
今日内容概要
- UDP代码编写(了解即可)
- 计算机核心理论(发展史)
- 进程理论
- 开启进程的诸多方式
- 进程join方法
- 进程间数据隔离
- IPC机制
内容详细
1、UDP代码编写
# 服务端
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM) # UDP协议
udp_sk.bind(('127.0.0.1', 9000)) # 绑定地址
msg, addr = udp_sk.recvfrom(1024) # 获取客户端说的话和地址
udp_sk.sendto(b'hi', addr) # 向客户端说话
udp_sk.close()
# 客户端
import socket
ip_port = ('127.0.0.1', 9000) # 想访问的服务端地址
udp_sk = socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello', ip_port) # 向ip_port说话
back_msg, addr = udp_sk.recvfrom(1024) # 获取服务端回复的话和服务端地址
print(back_msg.decode('utf-8'), addr)
"""
时间服务器的实现原理
1.内部小电容供电
2.远程时间同步
"""
2、操作系统发展史
"""学习并发编程其实就是在学习操作系统的发展史(底层逻辑)"""
# 1.穿孔卡片时代
CPU的利用率极低
# 2.联机批处理系统
将多个程序员的程序一次性录入磁带中 之后交由输入机输入并由CPU执行
# 3.脱机批处理系统
现代计算机的雏形(远程输入 高速磁带 主机)
3、多道技术
'''针对处理器单核的情况'''
# 1.单道技术:
主要是串行状态 cpu利用率极低
# 2.多道技术
切换+保存状态 提升cpu利用率
"""
CPU工作机制
1.当某个程序进入IO(输入输出)状态的时候 操作系统会自动剥夺该程序的CPU执行权限
2.当某个程序长时间占用CPU的时候 操作系统也会剥夺该程序的CPU执行权限
"""
# 3.并行与并发
并行:多个程序同时执行(针对多核处理器)
并发:多个程序只要看起来像同时运行即可(针对单核处理器)
'''
单核CPU能否实现并行?
肯定不能,但是可以实现并发
12306可以同一时间支持几个亿的用户买票 问是并行还是并发?
肯定是并发(高并发)
星轨:微博(高并发)能够支持八个星轨(明星出轨)
'''
4、进程理论
# 1.进程与程序的区别(总结)
程序:一堆代码(死的)
进程:正在运行的程序(活的)
# 2.单核情况下的进程调度
要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是需要遵循一定的法则,由此就有了进程的调度算法
进程调度算法演变:
1.FCFS 先来先服务
对短作业不友好
2.短作业优先调度算法
对长作业不友好
3.时间片轮转法+多级反馈队列
先分配给新的多个进程相同的时间片
之后根据进程消耗的时间片多少分类别
每次有新的进程要启动 都会重新分配时间片
需要消耗更长时间启动的进程
会参与每次时间片的获取
每次获取时间片后的进度会被保留 并反馈给CPU
在下次分配时间片时会 在原进度基础上继续获取
直至启动完成
......
# 3.进程三状态
就绪态 运行态 阻塞态
'''进程要想进入运行态必须先经过就绪态'''
# 4.同步与异步
'''用于描述任务的提交方式'''
同步:提交完任务之后原地等待任务的返回结果 期间不做任何事
异步:提交完任务之后不原地等待任务的返回结果 直接去做其他事 结果由反馈机制自动提醒
# 5.阻塞与非阻塞
'''用于描述任务的执行状态'''
阻塞:阻塞态
非阻塞:就绪态 运行态
# 同步阻塞形式
效率最低
就是你专心排队,什么别的事都不做
# 异步阻塞形式
如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发(通知),也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面
异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞
# 同步非阻塞形式
实际上是效率低下的
想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的
# 异步非阻塞形式
效率更高
因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换
比如说,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下,那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了
"""
很多人会把同步和阻塞混淆,是因为很多时候同步操作会以阻塞的形式表现出来,同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的IO操作处被阻塞
"""
4.1、创建进程
1. 在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)
2. 在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。
"""
关于创建子进程,UNIX和windows
1.相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程
2.不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的
"""
4.2、代码层面创建进程
# 创建进程方式一:
import os
from multiprocessing import Process
import time
def test(name):
print('%s正在运行' % name)
time.sleep(3)
print('%s已经结束' % name)
if __name__ == '__main__':
p = Process(target=test, args=('jason',)) # 生成一个进程对象
p.start() # 告诉操作系统开设一个新的(子)进程 异步提交
print(os.getpid()) # 获取子进程号
print(os.getppid()) # 获取父进程号
print('主')
执行结果:
11876
7404
主 # 主程序不会等子程序执行完毕后再执行
jason正在运行
jason已经结束
"""
在windows中开设进程类似于导入模块
从上往下再次执行代码
一定需要在__main__判断语句内执行开设进程的代码
在linux中是直接将代码完整的复制一份执行
不需要在__main__判断语句内执行
"""
# 创建进程方式二:
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self, name):
super().__init__()
self.name = name
def run(self): # 固定格式 用 run
print('%s正在运行' % self.name)
time.sleep(3)
print('%s已经结束' % self.name)
if __name__ == '__main__':
p = MyProcess('jack')
p.start()
print('主进程')
执行结果:
主进程 # 主程序不会等子程序执行完毕后再执行
jack正在运行
jack已经结束
4.3、进程的join方法
'''
要求:
主进程执行结果要在子进程执行完毕之后(串行)
'''
# 1.验证join方法
from multiprocessing import Process
import time
def test(name):
print('%s开始运行' % name)
time.sleep(3)
print('%s结束' % name)
if __name__ == '__main__':
p = Process(target=test, args=('jason',))
p.start()
p.join() # 等待子进程运行完毕之后 再继续运行主进程代码(串行)
print('主进程')
# 执行结果: # 主进程等待子进程执行完毕之后再继续执行
jason开始运行
jason结束
主进程
# 2.验证p.start()功能
from multiprocessing import Process
import time
def test(name):
print('%s开始运行' % name)
time.sleep(3)
print('%s结束' % name)
if __name__ == '__main__':
p = Process(target=test, args=('jason',))
p1 = Process(target=test, args=('tony',))
p2 = Process(target=test, args=('ly',))
p.start()
p1.start()
p2.start()
p.join() # 等待子进程运行完毕之后 再继续运行主进程代码(串行)
print('主进程')
执行结果: # 随机运行子进程
tony开始运行
jason开始运行
ly开始运行
jason结束
tony结束
ly结束
主进程
# 3.验证join方法的串行状态
from multiprocessing import Process
import time
def test(name):
print('%s开始运行' % name)
time.sleep(3)
print('%s结束' % name)
if __name__ == '__main__':
start_time = time.time()
for i in range(1, 4):
p = Process(target=test, args=(i, ))
p.start()
p.join() # 等待子进程运行完毕之后 再继续运行主进程代码(串行)
print(time.time() - start_time)
执行结果: # 每个子进程都是三秒时间 主进程结束至少9秒以上
1开始运行
1结束
2开始运行
2结束
3开始运行
3结束
9.431007862091064
# 4.提升CPU效率 解决串行状态的无效等待
from multiprocessing import Process
import time
def test(name, n):
print('%s开始运行' % name)
time.sleep(n)
print('%s结束' % name)
if __name__ == '__main__':
p_list = []
start_time = time.time()
for i in range(1, 4):
p = Process(target=test, args=(i, i))
p.start()
p_list.append(p)
for p in p_list:
p.join()
print(time.time() - start_time) # 主程序执行时间 永远依据需要最长时间的子进程 而不是所有进程时间相加
执行结果:
1开始运行
2开始运行
3开始运行
1结束
2结束
3结束
3.1535840034484863
4.4、进程间默认无法交互
# 进程间数据是相互隔离的
from multiprocessing import Process
money = 100
def test():
global money
money = 999
if __name__ == '__main__':
p = Process(target=test)
p.start()
# 先确保子进程运行完毕了 再打印
p.join()
print(money)
执行结果:
100
'''
因为start启动进程 原理是 按照导入模块的方式从上自下执行代码
在test()中执行的global 只是更改了子进程自己的全局money
最后的print(money) 输出的是主进程的 money 所以结果没变
证明 子进程和主进程之间的数据是无法交互的
'''
"""
如果在局部修改全局数据:
如果数据为不可变类型 则需要使用关键字 global 声明
如果数据为可变类型 则无需使用关键字 global 声明
如果想要在内部的局部修改外部局部的不可变数据类型:
需要关键字nonlocal声明
"""
5、进程对象方法
1.current_process查看进程号
2.os.getpid() 查看进程号 os.getppid() 查看父进程进程号
from multiprocessing import current_process
import os
print(os.getpid()) # 11660 获取当前进程号
print(os.getppid()) # 7404 获取当前进程父进程编号
print(current_process().pid) # 11660 获取当前进程号
3.进程的名字,p.name直接默认就有,也可以在实例化进程对象的时候通过关键字形式传入name=''
4.p.terminate() 杀死子进程
5.p.is_alive() 判断进程是否存活
from multiprocessing import Process
import time
def test(name, n):
print('%s开始运行' % name)
time.sleep(n)
print('%s结束' % name)
if __name__ == '__main__':
p = Process(target=test, args=('jason', 3))
p.start()
p.terminate() # 杀死子进程
time.sleep(0.1) # 不加等待0.1秒的话 因为计算机获取结果过快 会一致提示进程是存活状态
print(p.is_alive()) # 判断进程是否存活
print('主进程')
print(p.name)
执行结果:
False
主进程
Process-1