python基础(下)
2020-11-15 20:23:25 0 举报
AI智能生成
python语法基础 下
作者其他创作
大纲/内容
进程
基础概念
进程
进程( process )就是正在运行的程序, 它是操作系统中, 资源分配的最小单位
资源分配: 分配的是cpu和内存等物理资源
进程号是进程的唯一标识
linux
ps -aux 查看进程号
ps -ef | grep 2784 过滤查找2784这个进程
kill -9 进程号 强行杀死进程
python
os.getpid() 获取当前进程号
os.getppid() 获取当前进程的父进程
同一个程序执行两次之后是两个进程
进程和进程之间的关系: 数据彼此隔离, 通过socket通信
并发和并行
并发: 一个cpu同一时间不停执行多个程序
并行: 多个cpu同一时间不停执行多个程序
cpu的进程调度方法
先来先服务fcfs(first come first server): 先来的先执行
短作业优先算法: 分配的cpu多, 先把短的算完
时间片轮转算法:每一个任务就执行一个时间片的时间, 然后就执行其他的.
多级反馈队列算法
进程三状态
就绪(Ready)状态
只等CPU执行, 其他所有资源都已分配完毕, 称为就绪状态。
执行(Running)状态
cpu开始执行该进程时称为执行状态。
阻塞(Blocked)状态
由于等待某个事件发生而无法执行时, 便是阻塞状态, cpu不等待, 先执行其它进程
例如等待I/O完成input, 申请缓冲区不能满足等等。
同步 异步 / 阻塞 非阻塞
场景: 在多任务当中
同步: 必须等我这件事结束, 你再开始, 只有一条主线
异步: 没等我这件事结束, 你就开始了, 有两条主线
阻塞: 比如代码有了input, 就是阻塞, 必须要输入一个字符串, 否则代码不往下执行
非阻塞: 没有任何等待, 正常代码往下执行
同步阻塞: 效率低, cpu利用不充分
异步阻塞: 比如socketserver, 可以同时连接多个, 但是彼此都有recv
同步非阻塞: 没有类似input的代码, 从上到下执行, 默认的正常情况代码
异步非阻塞: 效率是最高的, cpu过度充分, 过度发热
基本使用
基本语法
导入
from multiprocessing import Process
创建子进程, 返回进程对象
p = Process(target=func)
调用子进程
p.start()
注意: 创建子进程, 会把当前主进程的代码都复制一份过去, 全部执行一遍
windows为了防止无限创建子进程, 必须判断__name__是否为main
linux可以不判断, 系统自动处理了
带参数的进程
target=指定任务 args=参数元组
p = Process(target=func, args=(n,))
进程间数据独立
不同堆空间
进程间异步
多个进程之间是异步的并发程序, 且由于cpu调度策略, 不确定各个进程执行顺序
一般主进程执行速度稍快于子进程, 因为子进程创建时需要等分配资源, 短暂进入阻塞态
为了方便进程管理, 默认主进程在所有子进程执行结束后才关闭, 最后释放资源
若不等待, 子进程可能不停在后台运行, 占用cpu和内存资源形成僵尸进程
同步子父进程: join
基本语法
使用 p.join(), 必须等待当前子进程执行结束, 才去执行下面的代码
多进程场景
join放在循环里会导致子进程变成同步, 所以要放外面, 分别 .join()
自定义进程类
基本语法
必须继承Process类
必须要有run()方法
带参数
继承的父类BaseProcess有很多初始化成员, 要用super()调用一下
守护进程
功能
守护进程守护的是主进程, 当主进程所有代码执行完毕之后, 立刻强制杀死守护进程
基本语法
在进程启动之前, 设置守护进程
p.daemon = True
多个子进程
把死循环的进程设为守护进程
模拟: 监控报活
使用join()让子线程先结束, 间接守护该子线程
进程间通信
互斥锁 Lock
概念
是一种基于socket的IPC
上锁 lock.acquire() 和解锁 lock.release() 是一对
连续上锁不解锁是死锁
只有解锁状态下其他进程才有机会上锁
基本语法
导入
from multiporcessing import Lock
创建一把锁
lock = Lock()
上锁
lock.acquire()
解锁
lock.release()
模拟: 抢票程序
信号量 Semaphore
概念
lock + 计数器 实现, 本质是锁, 只不过是多个进程上多把锁, 可以控制上锁的数量
基本语法
导入
from multiporcessing import Semaphore
同一时间允许多个进程上5把锁
sem = Semaphore(5)
上锁
sem.acquire()
解锁
sem.release()
模拟: 唱歌房
事件 Event
概念
如果有进程需要通过判断某个其他进程的阻塞状态来确定自己下一步的操作, 为了解决这种进程同步问题引入了Event
基本语法
导入
from multiporcessing import Event
生成事件对象e
e = Event()
判断当前的属性是否为True (默认False)
print(e.is_set())
将这个属性的值改成True
e.set()
将这个属性的值改成False
e.clear()
是False则等待(阻塞), 最多3s
e.wait(3)
模拟: 红绿灯
进程队列 Queue
概念
基于管道和锁, 能传输更具体的数据
基本语法
导入
from multiporcessing import Queue
创建进程队列
q = Queue()
(5)可以指定队列长度为5
存放
q.put()
队列长度一定时, 塞入过多数据会阻塞
put_nowait()
非阻塞版本的put, 超长则报错
获取
print(q.get())
在获取不到数据时会发生阻塞态
get_nowait()
拿不到数据报异常
windows能用 linux不兼容
检测队列元素个数
q.qsize()
示例: 不同进程间能出队入队同一容器的数据
生产者消费者模型
生产者负责生产存储数据(put)
消费者负责使用数据(get)
比较理想的模型
生产多少消费多少
生产数据的速度和消费数据速度相对一致
示例: 通过进程队列存放和消费数据
JoinableQueue 队列
概念
类似队列, 增加了一个计数器属性, 可以通过join判断计数器是否为0来自动放行
基本语法
导入
from multiporcessing import JoinableQueue
创建进程队列
jq = JoinableQueue()
(5)可以指定队列长度为5
存放
jq.put()
内置计数器属性值自动+1
获取
jq.get()
计数器属性值-1
jq.task_done()
判断是否放行
jq.join()
示例: 优化生产者和消费者模型
Manager 数据共享
Manager().list, Manager().dict, 实现多进程间数据共享
创建同进程队列, 用法同普通列表/字典
线程
基础概念
线程是计算机中调度的最小单位
线程的缘起
内存空间有限, 不能创建太多进程
线程的特点
比较轻量级, 一个进程中的所有线程资源是共享的
python线程的缺陷
可以并发, 但是不能并行
原因
全局解释器锁(Cpython解释器特有) GIL锁
想要并行的解决办法(都不治标)
用多进程间接实现线程的并行
换一个Pypy, Jpython解释器
程序分为计算密集型和io密集型
计算密集型程序会过度依赖cpu
网页, 爬虫, OA办公等io密集型程序, python绰绰有余
基本使用
from threading import Thread
导入, 创建, 启动, 同步等用法同进程
一个进程包含多个线程
并发时, 多线程速度比多进程快很多
多线程间数据共享
自定义线程类
必须继承父类Thread, 必须要有run()
线程的相关属性
线程.is_alive()
检测线程是否仍然存在
设置查看进程名
线程.setName()
设置线程名字
线程.getName()
获取线程名字
查看进程号
currentThread().ident
查看线程id号
enumerate()
返回目前正在运行的线程列表
activeCount()
返回目前正在运行的线程数量
守护线程
t.setDaemon(True)
与进程不同, 等待所有线程全部执行完毕后, 自己才终止, 守护所有线程
线程中的数据安全
互斥锁
示例: 通过计算n的值判断数据是否安全
with语法可以简化上锁+解锁的操作, 自动完成
信号量
死锁和递归锁
语法死锁
同一把锁连续acquire不释放
逻辑死锁
两把锁必须配合使用才会放行, 但有时一个线程不能同时抢到两个锁资源
递归锁
功能
用于快速解决线上项目死锁问题
用法
from threading import RLock
让noodles_lock和chopsticks_lock都等于递归锁
事件 Event
用法同进程的Event
模拟: 连接数据库超时
线程队列
方法同进程队列, 但有三种结构
Queue 单调队列
用法同multiprocessing.Queue
LifoQueue 栈
lifo: last in, first out
PriorityQueue 优先级队列
按照优先级顺序进行排序存放(默认从小到大)
在一个优先级队列中要放同一类型数据, 不能混合使用
放入容器型数据, 容器每个索引对应的元素类型需要相同, 只有集合可以不同
进程池/线程池
概念
固定进程/线程来执行所有任务, 系统不会额外创建多余的进程/线程
优点
通过重复利用已创建的进程/线程降低创建和销毁造成的消耗
任务可以不需要等到进程/线程创建就能立即执行
提高进程/线程的可管理性
用法
进程池
导入
from concurrent.futures import ProcessPoolExecutor
创建进程池对象
p = ProcessPoolExecutor()
参数是进程数, 默认是 系统最大的逻辑核心数
获取逻辑处理器数 os.cpu_count()
异步提交任务
p.submit(func, i)
submit(任务, 参数1, 参数2...)
极短作业会被单进程直接执行完, 可以节省系统资源
获取当前任务的返回值
obj.result()
不能在提交后调用obj.result(), 会阻塞, 导致进程同步
还是先把obj放列表中存着, 最后在主进程中一起调用
等待所有进程池里的进程执行完毕后再放行
p.shutdown()
线程池
导入
from concurrent.futures import ThreadPoolExecutor
创建线程池对象
t = ThreadPoolExector()
参数是线程数, 默认是 系统最大的逻辑核心数 * 5
其他用法同进程池
map
相当于返回值为迭代器的submit
进程池对象.map(任务函数, Iterable)
线程池对象.map(任务函数, Iterable)
回调函数
概念
回头调用一下函数获取最后结果
功能
为了获取最后的状态值, 使用回调
用法
python中, 通过 add_done_callback(自定义回调函数), 在进程/线程结束后, 异步执行自定义函数
示例: 查看回调函数的进程/线程id
进程池的回调函数由主进程执行, 且为同一线程
线程池的回调函数由子线程执行
协程
概念
协程是线程实现的具体方式
功能
用户在单线程内控制协程的切换, 提升效率
提高更大量的并行并发
在进程一定的情况下, 开辟多个线程
在线程一定的情况下, 创建多个协程
协程版生产者消费者模型
协程版本
greenlet 早期版本
switch 手动切换任务
gevent
缺点: 不能识别所有阻塞
monkey补丁 协程终极版本
协程使用
基本使用
g = gevent.spawn(func, arg1, arg2 ...)
启动协程
g1.join()
协程任务结束再放行
joinall
g1.join(); g2.join() <=> gevent.joinall([g1, g2 ...])
g1.value()
获取协程任务中的返回值
示例: 遇到阻塞自动切换协程
利用协程高效爬取数据
网络编程基础
理论基础
两大架构
C/S 架构
B/S 架构
主机标识
mac地址(物理地址)
ip 地址(逻辑地址Internet Protocol Address)
ipv4
32位的二进制数
ipv6
128位的二进制数
地址范围2^128-1
网段
作用
主要用来划分同一区域里的某些机器是否能够互相通信
在一个网段里可以不同过因特网, 直接对话
依据
如果IP地址和子网掩码相与得到的值相同就是同一网段
端口
概念
具体某个程序与外界通讯的出口
取值范围
0~65535
192.168.2.1:8000
通过ip和端口, 可以访问这个世界上任何一个电脑里的任何一个软件
自定义端口时, 最好命名8000以上的端口号
常用端口
20 : FTP文件传输协议(默认数据口)
21 : FTP文件传输协议(控制)
22 : SSH远程登录协议
25 : SMTP服务器所开放的端口,用于发送邮件
80 : http用于网页浏览,木马Executor开放此端口
443 : 基于TLS/SSL的网页浏览端口,能提供加密和通过安全端口传输的另一种HTTP => HTTPS
3306: MySQL开放此端口
OSI
网络七层模型
应表会传网数物
应用层 (应用层, 表示层, 会话层)
封装数据
根据不同的协议, 封装不同格式的数据
http (超文本传输协议)
HTTPS (加密传输的超文本传输协议)
FTP (文件传输协议)
SMTP (电子邮件传输协议)
传输层
封装端口
指定传输协议 (TCP协议/UDP协议)
socket抽象层
应用层传输层与网络层之间, 进行信息分割和拼接
网络层
封装ip
ipv4版本 / ipv6 (ICMP/IGMP)
数据链路层
封装mac地址
指定mac地址 (arp协议[ip->mac] / rarp协议[mac->ip])
物理层
打成数据包, 变成二进制的字节流, 通过网络进行传输
arp协议
交换机: 从下到上拆2层, 拆到数据链路层
路由器: 从下到上拆3层, 拆到网络层(得到对应的网段)
arp协议: 通过ip -> mac
rarp协议: 通过mac -> ip
arp协议整体是通过: 一次广播 + 一次单播 实现
TCP/UDP协议
TCP(Transmission Control Protocol)一种面向连接的、可靠的、传输层通信协议
优点: 可靠, 稳定, 传输完整稳定, 不限制数据大小
缺点: 慢, 效率低, 占用系统资源高, 一发一收都需要对方确认
应用:Web浏览器, 电子邮件, 文件传输, 大量数据传输的场景
UDP(User Datagram Protocol)一种无连接的, 不可靠的传输层通信协议
优点: 速度快, 可以多人同时聊天, 耗费资源少, 不需要建立连接
缺点: 不稳定, 不能保证每次数据都能接收到
应用: IP电话, 实时视频会议, 聊天软件, 少量数据传输的场景
TCP通信流程
TCP三次握手
TCP发送数据
TCP四次挥手
socket
通络通信过程中, 信息拼接的工具 (中文:套接字)
socket模块
创建一个socket对象
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
SOCK_STREAM 基于TCP
SOCK_DGRAM 基于UDP
常用方法
socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0)
获取要连接的对端主机地址
sk.bind(address)
将套接字绑定到地址。address地址的格式取决于地址族
在AF_INET下,以元组(host,port)的形式表示地址
sk.listen(backlog)
开始监听传入连接
backlog指定在拒绝连接之前,可以挂起的最大连接数量
sk.setblocking(bool)
是否阻塞(默认True)
如果设置False,那么accept和recv时一旦无数据,则报错。
sk.accept()
接受连接并返回(conn, address)
其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址
sk.connect(address)
连接到address处的套接字
sk.connect_ex(address)
同上,只不过会有返回值,连接成功返回 0,失败返回编码如:10061
sk.close()
关闭套接字
sk.recv(bufsize[,flag])
接受套接字的数据
数据以字符串形式返回
bufsize指定最多可以接收的字节数
sk.recvfrom(bufsize[.flag])
与recv()类似,但返回值是(data, address)
其中data是包含接收数据的字符串,address是发送数据的套接字地址
sk.send(string[,flag])
将string中的数据发送到连接的套接字
返回值是要发送的字节数量
返回值可能小于string的字节大小。即:可能未将指定内容全部发送
sk.sendall(string[,flag])
将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据
成功返回None,失败则抛出异常
内部通过递归调用send,将所有内容发送出去
sk.sendto(string[,flag],address)
将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址
返回值是发送的字节数
该函数主要用于UDP协议
sk.settimeout(timeout)
设置套接字操作的超时期,timeout是一个浮点数,单位是秒
sk.getpeername()
返回连接套接字的远程地址
返回值通常是元组(ipaddr, port)
sk.getsockname()
返回套接字自己的地址
通常是一个元组(ipaddr, port)
sk.fileno()
套接字的文件描述符
socket.sendfile(file, offset=0, count=None)
发送文件, 但目前多数情况下并无什么卵用
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
一个端口绑定多个程序(仅在测试时使用)
TCP收发消息
服务端7步, 客户端4步
UDP收发消息
服务端4步, 客户端3步
黏包与解决
黏包现象
TCP协议是面向连接的无边界协议, 在发送数据时,会出现黏包现象
在发送端,由于在缓冲区两个数据小,发送的时间隔短,TCP会根据优化算法把这些数据合成一个发送
在接收端,由于在缓冲区没及时接受数据,截取数据时把多次发送的数据截取成一条,形成了黏包
解决黏包
场景
解决黏包场景
实时通讯, 需要阅读消息是什么
不需要解决黏包场景
下载或者上传文件
方法
控制接收字节数, 在发数据之前先发送真实数据的长度给接收方, 然后再发送真实数据
struck模块
pack()
打包, 把任意长度数字转换成具有固定4个字节长度的字节流
unpack()
解包, 把4个字节长度的值恢复成原来的数字,返回元组
socketserver模块
功能
socketserver 为了实现tcp协议, server端的并发
服务端通过不同线程与不同客户端建立连接, 互不干扰
注意
新建子类MyServer,继承 socketserver.BaseRequestHandler, 重写handle方法, 一切数据收发操作都通过handle来调用
通过socketserver.ThreadingTCPServer() 创建server对象
通过serve_forever() 永久启动服务
加密
hashlib模块
md5算法
功能
生成结果是固定的128 bit字节, 通常用一个32位的16进制字符串表示
基本用法
创建md5对象
hs = hashlib.md5()
把要加密的数据更新到对象中
hs.update("111222".encode("utf-8"))
获取十六进制的字符串
res = hs.hexdigest()
加盐
加key, 可以在创建md5 HASH object的时候就塞一个约定好的密钥进去
动态加盐
使用random.randrange() 获得随机数字再编码 作为salt
使用os.urandom(字节数) 直接获得随机bytes 作为salt
sha系列
hs = hashlib.sha1() # 结果是固定长度40位的十六进制字符串
hs = hashlib.sha512("user".encode()) # 结果是固定长度128位的十六进制字符串
hmac模块
与普通hash算法相似, 但在创建对象时必须带上salt
hm = hmac.new(key, msg)
应用
加密存储密码
注册登录时, 把username作为盐, hash密码后再传输存储, 保证数据泄露依然安全
密钥协商
C接收S发的随机数字, 通过共有的密钥加密后发给S校验
文件校验
小文件
直接使用read()比较或者hash()之后比较
也可以使用hashlib模块转为32位字符串比较
大文件
分段读取, 每次通过hashlib的update()添加到一起, 转为固定128字节长度, 再比较最终结果是否相同
0 条评论
下一页