P1 排序算法
1. 选择排序
1.1 算法步骤
- 首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
- 再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾
- 重复第二步,直到所有元素均排序完成
1.2 代码实现
def selectionSort(arr):
for i in range(len(arr) - 1):
minIndex = i
for j in range(i + 1, len(arr)):
if arr[j] < arr[minIndex]:
minIndex = j
if i != minIndex:
arr[i], arr[minIndex] = arr[minIndex], arr[i]
return arr
2. 快速排序
2.1 算法步骤
- 从数列中挑出一个元素,称为基准元素(pivot);
- 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区操作;
- 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序
2.2 代码实现
def quickSort(arr, left=None, right=None):
left = 0 if not isinstance(left,(int, float)) else left
right = len(arr)-1 if not isinstance(right,(int, float)) else right
if left < right:
partitionIndex = partition(arr, left, right)
quickSort(arr, left, partitionIndex-1)
quickSort(arr, partitionIndex+1, right)
return arr
def partition(arr, left, right):
pivot = left
index = pivot+1
i = index
while i <= right:
if arr[i] < arr[pivot]:
swap(arr, i, index)
index+=1
i+=1
swap(arr,pivot,index-1)
return index-1
def swap(arr, i, j):
arr[i], arr[j] = arr[j], arr[i]
def sub_sort(list_, low, high):
temp = list_[low]
while low < high:
while list_[high] >= temp and high > low:
high -= 1
list_[low] = list_[high]
while list_[low] < temp and low < high:
low += 1
list_[high] = list_[low]
list_[low] = temp
return low
def quicksort(list_, low, high):
"""
快速排序
:param list_: 待排序的列表
:param low: 列表第一个元素索引
:param high:最后一个元素索引
:return:排完序的列表
"""
if low < high:
key = sub_sort(list_, low, high)
quicksort(list_, low, key - 1)
quicksort(list_, key + 1, high)
return list_
3. 插入排序
3.1 算法步骤
- 将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
- 从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)
3.2 代码实现
def insertionSort(arr):
for i in range(len(arr)):
preIndex = i-1
current = arr[i]
while preIndex >= 0 and arr[preIndex] > current:
arr[preIndex+1] = arr[preIndex]
preIndex-=1
arr[preIndex+1] = current
return arr
P2 IO网络编程
1. 字节串与字符串
- 普通的ASCII编码字符串可以在前面加b转换为字节串,例如:b’hello’
- 字符串转换为字节串方法:str.encode()
- 字节串转换为字符串方法:bytes.decode()
2. 文件操作
获取文件对象后可以通过文件对象对文件进行各种操作,文件对象可以调用下列函数:
2.1 读取文件
- read([size])
- readline([size])
- readlines([sizeint])
如果文件对象是以读的方式获取的,该文件对象是一个可迭代对象,在for循环中可以迭代文件的每一行
f = open('test', 'r')
while True:
data = f.read(100)
if not data:
break
print(data)
2.2 with操作
??python中的with语句使用于对资源进行访问的场合,保证不管处理过程中是否发生错误或者异常都会执行规定的“清理”操作,释放被访问的资源,比如有文件读写后自动关闭、线程中锁的自动获取和释放等。
with语句的语法格式如下
with context_expression [as target(s)]:
with-body
??通过with方法可以不用close(),因为with生成的对象在语句块结束后会自动处理,所以也就不需要close了,但是这个文件对象只能在with语句块内使用
with open('file', 'r+') as f:
r.read()
3. 文件偏移量
3.1 定义
??打开一个文件进行操作时系统会自动生成一个记录,记录中描述了我们对文件的一系列操作。其中包括每次操作到的文件位置。文件的读写操作都是从这个位置开始进行的。
3.2 基本操作
3.2.1 tell()
??功能: 获取文件偏移量大小
3.2.2 seek(offset[,whence])
??功能:移动文件偏移量位置 ??参数:offset代表相对于某个位置移动的字节数。负数表示向前移动,正数表示向后移动。whence是基准位置,默认值为0,代表从文件开头算起,1代表从当前位置算起,2表示从文件末尾算起。 ??注意:必须以二进制方式打开文件时基准位置才能是1或者2
4. 文件描述符
4.1 定义
系统中每一个IO操作都会分配一个整数作为编号,该整数即这个IO操作的文件描述符
4.2 获取文件描述符
fileno(): ??通过IO对象获取对应的文件描述符
4.3 文件管理函数
- 获取文件大小
os.path.getsize(file) - 查看文件列表
os.listdir(dir) - 查看文件是否存在
os.path.exists(file) - 判断文件类型
os.path.isfile(file) - 删除文件
os.remove(file)
5. 网络模型
5.1 OSI七层模型
制定组织 ??ISO(国际标准化组织) 作用 ??使网络通信工作流程标准化
应用层: 提供用户服务,具体功能由应用程序实现 表示层: 数据的压缩优化加密 会话层: 建立用户级的连接,选择适当的传输服务 传输层: 提供传输服务 网络层: 路由选择,网络互联 链路层: 进行数据交换,控制具体数据的发送 物理层: 提供数据传输的硬件保证,网卡接口,传输介质
优点:
- 建立了统一的工作流程
- 分部清晰,各司其职,每个步骤分工明确
- 降低了各个模块之间的耦合度,便于开发
5.2 TCP/IP四层模型
网络协议
在网络数据传输中,都遵循的规定,包括建立什么样的数据结构,什么样的特殊标志等
5.3 网络基础概念
IP地址
- 功能:确定一台主机的网络路由位置
- 查看本机网络地址命令:ifconfig
域名
- 定义:给网络服务器地址起的名字
- 作用:方便记忆,表达一定的含义
- ping[ip]:测试和某个主机是否联通
端口号(port)
- 作用:端口是网络地址的一部分,用于区分主机上不同的网络应用程序
- 特点:一个系统中的应用监听端口不能重复
- 取值范围:1–65535
- 1–1023系统应用或者大众程序监听端口
- 1024–65535自用端口
5.4 传输层服务
5.4.1 面向连接的传输服务(基于TCP协议的数据传输)
传输特征:
- 提供了可靠的数据传输,可靠性指数据传输过程中无丢失,无失序,无差错,无重复
实现手段:
三次握手(建立连接)
- 客户端向服务器发送消息报文请求连接
- 服务器收到请求后,回复报文确定可以连接
- 客户端收到回复,发送最终报文连接建立
四次挥手(断开连接)
- 主动方发送报文请求断开连接
- 被动方收到消息后,立即回复,表示准备断开
- 被动方准备就绪,再次发送报文表示可以断开
- 主动方收到确定,发送最终报文完成断开
5.4.2 面向无连接的传输服务(基于UDP协议的数据传输)
传输特点:
- 不保证传输的可靠性,传输过程没有连接和断开,数据收发自由随意。
适用情况:
- 网络较差,对传输可靠性要求不高。比如:网络视频,群聊,广播
5.5 socket套接字编程
5.5.1 套接字介绍
套接字:
Python实现套接字编程:
- import socket
- from socket import socket
套接字分类
- 流式套接字(SOCK_STREAM):以字节流方式传输数据,实现tcp网络传输方案
- 数据报套接字:以数据报形式传输数据,实现udp网络传输方案。
5.5.2 tcp套接字
服务端流程: ??socket–>bind–>listen–>accept–>send/recv–>close 1. 创建套接字
sockfd = socket.socket(socket_family = AF_INET, socket_type = SOCK_STREAM, proto=0)
2. 绑定地址
- 本地地址:localhost 127.0.0.1
- 网络地址:172.40.91.185
- 自动获取地址:0.0.0.0
sockfd.bind(addr)
3. 设置监听
sockfd.listen(n)
4.等待处理客户端连接请求
connfd, addr = sockfd.accept()
5.消息收发
data = connfd.recv(buffersize)
n = connfd.send(data)
6.关闭套接字
sockfd.close()
代码示例
import socket
import unicodedata
sockfd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sockfd.bind(('127.0.0.1', 8888))
sockfd.listen(5)
while True:
print("Waiting for connect")
try:
connfd, addr = sockfd.accept()
print("Connect from", addr)
except KeyboardInterrupt:
print("Server Exit")
break
except Exception as e:
print(e)
continue
while True:
data = connfd.recv(1024)
if not data:
break
print("收到:", data.decode())
n = connfd.send(b'Thanks')
print("发送%d字节" % n)
connfd.close()
sockfd.close()
客户端流程 ??socket–>bind<可选>(一般不写)–>connect–>send/recv–>close
1.创建套接字
2.连接服务器
sockfd.connect((server_addr,port))
3.收发消息
4.关闭套接字
代码示例
"""
tcp_client.py tcp客户端流程
"""
from socket import *
sockfd = socket()
server_addr = ('127.0.0.1', 8888)
sockfd.connect(server_addr)
while True:
data = input("Msg>>")
if not data:
break
sockfd.send(data.encode())
data = sockfd.recv(1024)
print("Server:", data.decode())
sockfd.close()
5.5.3 tcp套接字数据传输特点
??tcp连接中当一端退出,另一端如果阻塞在recv,此时recv会立即返回一个空字串 tcp连接中如果一端已经不存在,仍然试图通过send发送则会产生BrokenPipeError 一个监听套接字可以同时连接多个客户端,也能够重复被连接
5.5.4 网络收发缓冲区
- 网络缓冲区有效的协调了消息的收发速度
- send和recv实际是向缓冲区发送接收消息,当缓冲区不为空recv就不会阻塞
5.5.5 tcp粘包
原因:
- tcp以字节流方式传输,无消息边界。多次发送的消息被一次接收,此时就会形成粘包
影响:
- 如果每次发送内容是一个独立的含义,需要接收端独立解析此时粘包会有影响
处理方法:
- 人为的添加消息边界
- 控制发送速度
5.5.6 UDP套接字
服务端流程 ??socket–>bind–>recvfrom–>sendto–>close
1.创建数据报套接字
sockfd = sock(AF_INET, SOCK_DGRAM)
2.绑定地址
sockfd.bind(addr)
3.消息收发
data, addr = sockfd.recvfrom(buffersize)
n = sockfd.sendto(data, addr)
4.关闭套接字
sockfd.close()
服务端代码示例
"""
udp_server.py udp套接字服务端流程
"""
import socket
sockfd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_addr = ('127.0.0.1', 8888)
sockfd.bind(server_addr)
while True:
data, addr = sockfd.recvfrom(1024)
print("收到消息:", data.decode())
sockfd.sendto(b'Thanks', addr)
socket.close()
udp客户端代码示例
from socket import *
ADDR = ('127.0.0.1', 8888)
sockfd = socket(AF_INET, SOCK_DGRAM)
while True:
data = input("Msg>>")
if not data:
break
sockfd.sendto(data.encode(), ADDR)
msg, addr = sockfd.recvfrom(1024)
print("From server:", msg.decode())
sockfd.close()
5.5.7 tcp套接字和udp套接字区别
- 流式套接字是以字节流方式传输数据,数据报套接字以数据报形式传输
- tcp套接字会有粘包,udp套接字有消息边界不会粘包
- tcp套接字保证消息的完整性,udp套接字则不能消息的完整性
- tcp套接字依赖listen accept建立连接才能收发消息,udp套接字则不需要
- tcp套接字使用send, recv收发消息,udp套接字使用sendto, recvfrom
5.5.8 socket套接字属性
- sockfd.type 套接字类型
- sockfd.family 套接字地址类型
- sockfd.getsockname() 获取套接字绑定地址
- sockfd.fileno() 获取套接字的文件描述符
- sockfd.getpeername() 获取连接套接字客户端地址
- sockfd.setsockopt(level,option,value)
??功能:设置套接字选项 ??参数:level选项类别 SOL_SOCKET ?????option 具体选项内容 ?????value 选项值 - sockfd.getsockopt(level, option)
??功能:获取套接字选项值
代码示例
"""
套接字属性介绍
"""
from socket import *
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, True)
s.listen(3)
c, addr = s.accept()
print("连接端地址:",c.getpeername())
print("地址类型:",s.family)
print("套接字类型:",s.type)
print("绑定地址:",s.getsockname())
print("文件描述符:",s.fileno())
5.5.8 UDP广播
- 广播定义: 一端发送多点接收
- 广播地址: 每个网络的最大地址为发送广播的地址,向该地址发送,则网段内所有主机都能接收
代码示例
"""
广播接收 broadcast_recv.py
1.创建udp套接字
2.设置套接字可以发送接收广播 (setsockopt)
3.选择接收的端口
4.接收广播
"""
from socket import *
s = socket(AF_INET, SOCK_DGRAM)
s.setsockopt(SOL_SOCKET, SO_BROADCAST, True)
s.bind(('0.0.0.0', 9999))
msg, addr = s.recvfrom(1024)
print(msg.decode())
"""
广播发送 broadcast_send.py
1.创建udp套接字
2.设置可以发送广播
3.循环向广播地址发送
"""
from socket import *
from time import sleep
dest = ('192.168.0.161', 9999)
s = socket(AF_INET, SOCK_DGRAM)
s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
data = """
******************
北京 11.17 初冬
温度:7
状态:没有四块五的妞
******************
"""
sleep(2)
s.sendto(data.encode(), dest)
5.5.9 TCP套接字之HTTP传输
1. HTTP协议(超文本传输协议)
- 用途:网页获取,数据的传输
- 特点:
??应用层协议,传输层使用tcp传输 ??简单,灵活,很多语言都有HTTP专门接口 ??无状态,协议不记录传输内容 ??http1.1支持持久连接,丰富了请求类型
2. HTTP请求(request)
?GET ?????/ ?????HTTP/1.1 请求类别 ??请求内容 ??协议版本
- 请求头:对请求的进一步解释和描述,组成格式是一个个的键值对,每个键值对占一行
Accept-Encoding: gzip
3. HTTP响应(response)
HTTP/1.1 ???200????OK 版本信息???响应码??附加信息
Content-Type: text/html
http请求和响应的代码示例
"""
httpserver v1.0
基本要求:
获取来自浏览器的请求
判断如果请求内容是/,将index.html响应给客户端
如果返回的是其他内容则响应404
"""
from socket import *
def request(connfd):
data = connfd.recv(4096)
if not data:
return
request_line = data.decode().split('\n')[0]
info = request_line.split(" ")[1]
if info == "/":
with open('index.html') as f:
response = "HTTP/1.1 200 OK\r\n"
response += "Content-Type:text/html\r\n"
response += "\r\n"
response += f.read()
else:
response = "HTTP/1.1 404 Not Found\r\n"
response += "Content-Type:text/html\r\n"
response += "\r\n"
response += "<h1>Sorry...</h1>"
connfd.send(response.encode())
sockfd = socket()
sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, True)
sockfd.bind(('0.0.0.0', 8000))
sockfd.listen(3)
while True:
connfd, addr = sockfd.accept()
request(connfd)
5.5.10 struct模块的使用
- 原理:将一组简单数据进行打包,转换为bytes格式发送。或者将一组bytes格式数据,进行解析
- 接口使用
import struct
st = struct.Struct(fmt)
st.pack(v1, v2, v3...)
st.unpack(bytes_data)
struct.pack(fmt, v1, v2, v3,...)
struct.unpack(fmt, bytes_data)
在终端演示:
In [1]: import struct
In [2]: st = struct.Struct('i4sf')
In [3]: data = st.pack(1, b'Lily', 1.65)
In [4]: data
Out[4]: b'\x01\x00\x00\x00Lily33\xd3?'
In [5]: st.unpack(data)
Out[5]: (1, b'Lily', 1.649999976158142)
struct模块的练习
使用udp完成
- 在客户端不断录入学生信息,将其发送到服务端
- 在服务端将学生信息写入到一个文件中,每个学生信息占一行
- 信息格式:id(int) name(str) age(int) score(float)
"""
struct_send.py
"""
from socket import *
import struct
st = struct.Struct('i32sif')
s = socket(AF_INET, SOCK_DGRAM)
ADDR = ('127.0.0.1', 8888)
while True:
print("=====================")
id = int(input("ID:"))
name = input("Name:").encode()
age = int(input("Age:"))
score = float(input("Score:"))
data = st.pack(id, name, age, score)
s.sendto(data, ADDR)
"""
struct_recv.py
"""
from socket import *
import struct
st = struct.Struct('i32sf')
s = socket(AF_INET, SOCK_DGRAM)
s.bind(('127.0.0.1', 8888))
f = open('student.txt', 'a')
while True:
data, addr = s.recvfrom(1024)
data = st.unpack(data)
info = "%d %-10s %d %.1f\n" % data
f.write(info)
f.flush()
6. 网络并发
6.1 多任务编程
- 意义: 充分利用计算机多核资源,提高程序的运行效率
- 实现方案: 多进程,多线程
- 并行与并发
??并发:同时处理多个任务,内核在任务间不断的切换达到好像多个任务被同时执行的效果,实际每个时刻只有一个任务占有内核 ??并行:多个任务利用计算机多核资源在同时执行,此时多个任务间为并行关系
6.2 进程(process)
1.定义:程序在计算机中的一次运行 ??程序是一个可执行的文件,是静态的占有磁盘 ??进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期
2.系统中如何产生一个进程 ??【1】用户空间通过调用程序接口或者命令发起请求 ??【2】操作系统接受用户请求,开始创建进程 ??【3】操作系统调配计算机资源,确定进程状态等 ??【4】操作系统将创建的进程提供给用户使用
3.进程基本概念
??cpu时间片:如果一个进程占有cpu内核则称这个进程在cpu时间片上
??PCB(进程控制块):在内存中开辟的一块空间,用于存放进程的基本信息,也用于系统查找识别进程
??进程ID(PID):系统为每个进程分配的一个大于0的整数,作为进程ID。每个进程ID不重复????Linux查看进程ID:ps -aux
??父子进程:系统中每一个进程(除了系统初始化进程)都有唯一的父进程,可以有0或多个子进程。父子进程关系便于进程管理。??查看进程树:pstree
??进程状态(三态): ????就绪态:进程具备执行条件,等待分配CPU资源 ????运行态:进程占有CPU时间片正在运行 ????等待态:进程暂时停止运行,让出CPU ??五态(在三态基础上增加新建和终止 ): ????新建:创建一个进程,获取资源的过程 ????终止:进程结束,释放资源的过程
??状态查看命令: ps -aux -->STAT列 ????S?等待态 ????R?运行态 ????D?等待态 ????T?等待态 ????Z?僵尸
????<?有较高优先级 ????N?优先级较低 ????+?前台进程 ????会话组组长 ????有多线程的
2. 进程的运行特征 ??【1】进程可以使用计算机多核资源 ??【2】进程是计算机分配资源的最小单位 ??【3】进程之间的运行互不影响,各自独立 ??【4】每个进程拥有独立的空间,各自使用自己空间资源
6.3 基于fork的多进程编程
"""fork创建进程演示"""
import os
pid = os.fork()
if pid < 0:
print("Create process failed")
elif pid == 0:
print("The new progress")
else:
print("The old process")
print("Fork test over")
注意:
- 子进程会复制父进程全部内存空间,从fork下一句开始执行
- 父子进程各自独立运行,运行顺序不一定
- 利用父子进程fork返回值的区别,配合if结构让父子进程执行不同的内容几乎是固定搭配
- 父子进程有各自特有特征比如PID,PCB, 命令集等
- 父进程fork之前开辟的空间子进程同样拥有,父子进程对各自空间的操作不会相互影响
6.4 进程相关函数
os.getpid()
os.getppid()
os._exit(status)
sys.exit([status])
代码示例
import os, sys
pid = os.fork()
if pid < 0:
print("Error")
elif pid == 0:
print("Child PID:", os.getpid())
print("Parent PID:", os.getppid())
else:
print("Get Child PID:", pid)
print("Parent PID:", os.getpid())
os._exit(0)
sys.exit("退出")
print("Exit")
6.5 孤儿和僵尸
1.孤儿进程: 父进程先于子进程退出,此时子进程成为孤儿进程 特点:孤儿进程会被系统进程收养,此时系统进程就会成为孤儿进程新的父进程,孤儿进程退出该进程会自动处理
2.僵尸进程: 子进程先于父进程退出,父进程又没有处理子进程的退出状态,此时子进程就会称为僵尸进程 特点: 僵尸进程虽然结束,但是会存留部分PCB在内存中,大量的僵尸进程会浪费系统的内存资源
3.如何避免僵尸进程产生
- 使用wait函数处理子进程退出
pid, status = os.wait()
代码示例
import os, sys
pid = os.fork()
if pid < 0:
print("Error")
elif pid == 0:
print("Child PID:", os.getpid())
sys.exit("子进程退出")
else:
"""
os.wait() 处理僵尸进程
"""
pid, status = os.wait()
print("pid:", pid)
print("status:", status)
while True:
pass
-
创建二级子进程处理僵尸 ??父进程创建子进程,等待回收子进程 ??子进程创建二级子进程然后退出 ??二级子进程称为孤儿,和一级子进程的父进程一同执行事件 -
通过信号处理子进程退出 ??原理: 子进程退出时会发送信号给父进程。如果父进程忽略子进程信号,则系统就会自动处理子进程退出 ??方法: 使用signal模块在父进程创建子进程前写如下语句
import signal signal.signal(signal.SIGCHLD,signal.SIG_IGN)
???? 代码示例
"""信号处理僵尸"""
import os, sys
import signal
signal.signal((signal.SIGCHLD, signal.SIG_IGN))
pid = os.fork()
if pid < 0:
print("Error")
elif pid == 0:
print("Child PID:", os.getpid())
sys.exit(2)
else:
while True:
pass
6.6 群聊聊天室
需求:
- 有人进入聊天室需要输入姓名,姓名不能重复
- 有人进入聊天室,其他人会收到通知: XXX进入了聊天室
- 一个人发消息,其他人会收到: XXX:xxxxxxxxxxxxxxxx
- 有人退出聊天室,其他人也会收到通知: XXX退出了聊天室
- 扩展功能:服务器可以向所有用户发送公告:管理员消息:xxxxx
代码实现
客户端代码
"""chat_client.py"""
import os
import sys
from socket import *
ADDR = ('127.0.0.1', 8888)
def send_msg(s, name):
while True:
try:
text = input("发言:")
except KeyboardInterrupt:
text = 'quit'
if text.strip() == 'quit':
msg = 'Q' + ' ' + name
s.sendto(msg.encode(), ADDR)
sys.exit("退出聊天室")
msg = 'C %s %s' % (name, text)
s.sendto(msg.encode(), ADDR)
def recv_msg(s):
while True:
try:
data, addr = s.recvfrom(4096)
except KeyboardInterrupt:
sys.exit()
if data.decode() == 'EXIT':
sys.exit()
print(data.decode() + '\n发言:', end='')
def main():
s = socket(AF_INET, SOCK_DGRAM)
while True:
name = input("请输入姓名:")
msg = 'L' + ' ' + name
s.sendto(msg.encode(), ADDR)
data, addr = s.recvfrom(128)
if data.decode() == 'OK':
print("您已进入聊天室")
break
else:
print(data.decode())
pid = os.fork()
if pid < 0:
sys.exit("Error!")
elif pid == 0:
send_msg(s, name)
else:
recv_msg(s)
main()
服务端代码
"""chat_server"""
from socket import *
import os, sys
ADDR = ('0.0.0.0', 8888)
user = {}
def do_login(s, name, addr):
if name in user:
s.sendto("该用户已存在".encode(), addr)
return
s.sendto(b'OK', addr)
msg = "\n欢迎%s进入聊天室" % name
for i in user:
s.sendto(msg.encode(), user[i])
user[name] = addr
def do_chat(s, name, text):
msg = "\n%s: %s" % (name, text)
for i in user:
if i != name:
s.sendto(msg.encode(), user[i])
def do_quit(s, name):
msg = "\n%s 退出聊天室" % name
for i in user:
if i != name:
s.sendto(msg.encode(), user[i])
else:
s.sendto(b'EXIT', user[i])
del user[name]
def do_request(s):
while True:
data, addr = s.recvfrom(1024)
temp = data.decode().split(' ')
if temp[0] == 'L':
do_login(s, temp[1], addr)
elif temp[0] == 'C':
text = ' '.join(temp[2:])
do_chat(s, temp[1], text)
elif temp[0] == 'Q':
do_quit(s, temp[1])
def main():
s = socket(AF_INET, SOCK_DGRAM)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, True)
s.bind(ADDR)
pid = os.fork()
if pid == 0:
while True:
msg = input("管理员消息:")
msg = "C 管理员 " + msg
s.sendto(msg.encode(), ADDR)
do_request(s)
main()
6.7 multiprocessing模块创建进程
6.7.1 流程特点
- 将需要子进程执行的事件封装为函数
- 通过模块的Process类创建进程对象,关联函数
- 可以通过进程对象设置进程信息及属性
- 通过进程对象调用start启动进程
- 通过进程对象调用join回收进程
6.7.2 基本接口使用
注意:
- 启动进程此时target绑定函数开始执行,该函数作为子进程执行内容,此时进程真正被创建
注意:
- 使用multiprocessing创建进程同样是子进程复制父进程空间代码段,父子进程运行互不影响
- 子进程只运行target绑定的函数部分,其余内容均是父进程执行内容
- multiprocessing中父进程往往只用来创建子进程回收子进程,具体事件由子进程完成
- multiprocessing创建的子进程中无法使用标准输入(在子进程中无法使用input)
6.7.3 multiprocessing模块创建进程
"""
multiprocessing 模块创建进程
1.编写进程函数
2.生成进程对象
3.启动进程
4.回收进程
"""
import multiprocessing as mp
from time import sleep
def fun():
print("开始一个进程")
sleep(5)
print("子进程结束")
p = mp.Process(target=fun)
p.start()
sleep(3)
print("父进程执行的部分写在start和join间")
p.join()
6.7.4 multiprocessing模块创建多个进程
"""
multiprocessing 创建多个进程
"""
from multiprocessing import Process
from time import sleep
import os
def th1():
sleep(3)
print("吃饭")
print(os.getppid(), '--', os.getpid())
def th2():
sleep(2)
print("睡觉")
print(os.getppid(), '--', os.getpid())
def th3():
sleep(4)
print("打豆豆")
print(os.getppid(), '--', os.getpid())
things = [th1, th2, th3]
jobs = []
for th in things:
p = Process(target=th)
jobs.append(p)
p.start()
for i in jobs:
i.join()
上面代码运行结果
6.7.5 给进程函数传参
"""
Process 给进程函数传参
"""
from multiprocessing import Process
from time import sleep
def worker(sec, name):
for i in range(3):
sleep(sec)
print("I'm %s" % name)
print("I'm working...")
p = Process(target=worker, args=(2,), kwargs={'name': 'Baron'})
p.start()
p.join()
6.7.6 进程对象属性
- p.name 进程名称
- p.pid 对应子进程的PID号
- p.is_alive() 查看子进程是否在生命周期
- p.daemon 设置父子进程的退出关系
??如果设置为True则子进程会随父进程的退出而结束 ??要求必须在start前设置 ??如果daemon设置成True通常就不会使用join()
"""
进程对象属性
"""
import time
from multiprocessing import Process
def tm():
for i in range(3):
time.sleep(2)
print(time.ctime())
p = Process(target=tm, name="pname")
p.daemon = True
p.start()
print("进程名称:", p.name)
print("进程ID:", p.pid)
print("is alive:", p.is_alive())
6.8 进程池
6.8.1 必要性
??进程的创建和销毁过程消耗的资源较多,当任务量众多,每个任务在很短时间内完成时,需要频繁的创建和销毁进程。此时对计算机压力较大,进程池技术很好的解决了以上问题
6.8.2 原理
??创建一定数量的进程来处理事件,事件处理完进程不退出而是继续处理其他事件,直到所有事件全都处理完毕统一销毁。增加进程的重复利用,降低资源消耗
6.8.3 进程池的实现
- 创建进程池对象,放入适当的进程
from multiprocessing import Pool
Pool(processes)
- 将事件加入进程池队列执行
pool.apply_async(func,args,kwargs)
- 关闭进程池
pool.close()
- 回收进程池中进程
pool.join()
6.8.4 代码示例
"""
进程池使用示例
"""
from multiprocessing import Pool
from time import ctime, sleep
def worker(msg):
sleep(2)
print(ctime(), '--', msg)
pool = Pool(4)
for i in range(10):
msg = "Tedu %d" % i
pool.apply_async(func=worker, args=(msg,))
pool.close()
pool.join()
运行结果
6.9 进程间通信(IPC)
1.必要性: ??进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信 2.常用进程间通信方法: ??管道 ?消息队列 ?共享内存? 信号? 信号量 ?套接字
6.9.1 管道通信
- 通信原理
??在内存中开辟管道空间,生成管道操作对象,多个进程使用同一个管道对象进行读写即可实现通信 - 实现方法
from multiprocessing import Pipe
fd1,fd2 = Pipe(duplex=True)
fd.recv()
fd.send(data)
- 代码示例
"""
pipe.py 管道通信
注意:
1.multiprocessing中管道通信只能用于有亲缘关系的进程中
2.管道对象在父进程中创建,子进程通过父进程获取
"""
from multiprocessing import Process, Pipe
fd1, fd2 = Pipe()
def app1():
print("启动app1,请登录")
print("请求app2授权")
fd1.send("app1 请求登录")
data = fd1.recv()
if data:
print("登录成功:", data)
def app2():
data = fd2.recv()
print(data)
fd2.send(('Dave', '123'))
p1 = Process(target=app1)
p2 = Process(target=app2)
p1.start()
p2.start()
p1.join()
p2.join()
运行截图
6.9.2 消息队列
- 通信原理
??在内存中建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信 - 实现方法
from multiprocessing import Queue
q = Queue(maxsize=0)
q.put(data,[block,timeout])
q.get([block,timeout])
q.full()
q.empty()
q.qsize()
q.close()
- 代码示例
"""
queue_0.py 消息队列演示
注意:
消息队列符合先进先出原则
"""
from multiprocessing import Queue, Process
from time import sleep
from random import randint
q = Queue(5)
def handle():
for i in range(6):
x = randint(1, 33)
q.put(x)
q.put(randint(1, 16))
def request():
list_ = []
for i in range(6):
list_.append(q.get())
list_.sort()
list_.append(q.get())
print(list_)
p1 = Process(target=handle)
p2 = Process(target=request)
p1.start()
p2.start()
p1.join()
p2.join()
6.9.3 共享内存
- 通信原理
??在内存中开辟一块空间,进程可以写入内容和读取内容完成通信,但是每次写入内容会覆盖之前的内容 - 实现方法
from multiprocessing import Value, Array
obj = Value(ctype, data)
obj.value
obj.Array(ctype, data)
"""
value.py 开辟单一共享内存空间
注意:共享内存只能有一个值
"""
from multiprocessing import Process, Value
import time
import random
money = Value('i', 5000)
def man():
for i in range(30):
time.sleep(0.2)
money.value += random.randint(1, 1000)
def girl():
for i in range(30):
time.sleep(0.15)
money.value -= random.randint(100, 800)
p1 = Process(target=man)
p2 = Process(target=girl)
p1.start()
p2.start()
p1.join()
p2.join()
print("一个月余额", money.value)
"""
array.py 共享内存存放一组数据
"""
from multiprocessing import Process, Array
shm = Array('c', b'hello')
def func():
for i in shm:
print(i)
p = Process(target=func)
p.start()
p.join()
6.9.4 信号量
- 通信原理
??给定一个数量对多个进程可见。多个进程都可以操作该数量增减,并根据数量值决定自己的行为。 - 实现方法
from multiprocessing import Semaphore
sem = Semaphore(num)
sem.acquire()
sem.release()
sem.get_value()
- 代码示例
"""
sem.py 信号量演示
思路:信号量数量相当于资源,执行任务必须消耗资源
"""
from multiprocessing import Semaphore, Process
from time import sleep
import os
sem = Semaphore(3)
def handle():
sem.acquire()
print("%s 执行任务" % os.getpid())
sleep(2)
print("%s 执行任务完毕" % os.getpid())
sem.release()
for i in range(10):
p = Process(target=handle)
p.start()
7. 线程编程
7.1 线程基本概念
7.1.1 什么是线程
- 线程被称为轻量级的进程
- 线程也可以使用计算机多核资源,是多任务编程方式
- 线程是系统分配内核的最小单元
- 线程可以理解为进程的分支任务
7.1.2 线程特征
- 一个进程中可以包含多个线程
- 线程也是一个运行行为,消耗计算机资源
- 一个进程中的所有线程共享这个进程的资源
- 多个线程之间的运行互不影响各自运行
- 线程的创建和销毁消耗资源远小于进程
- 各个线程也有自己的ID等特征
7.2 threading模块创建线程
- 创建线程对象
from threading import Thread
t = Thread()
- 启动线程
t.start()
- 回收线程
t.join([timeout])
代码示例
"""
thread.py 线程基本使用
步骤:
1.封装线程函数
2.创建线程对象
3.启动线程
4.回收线程
"""
import threading
from time import sleep
import os
def music():
for i in range(3):
sleep(2)
print(os.getpid(), "播放:黄河大合唱")
t = threading.Thread(target=music)
t.start()
for i in range(4):
sleep(1)
print(os.getpid(), "播放:葫芦娃")
t.join()
"""
thread 线程函数参数演示
"""
from threading import Thread
from time import sleep
def func(sec, name):
print("线程函数参数")
sleep(sec)
print("%s执行完毕" % name)
jobs = []
for i in range(5):
t = Thread(target=func, args=(2,), kwargs={'name': 'T%d' % i})
jobs.append(t)
t.start()
for i in jobs:
i.join()
7.3 线程对象属性
- t.name 线程名称
- t.setName() 设置线程名称
- t.getName() 获取线程名称
- t.is_alive() 查看线程是否在生命周期
- t.daemon 设置主线程和分支线程的退出关系
- t.setDaemon() 设置daemon属性值
- t.isDaemon() 查看daemon属性值
daemon为True时主线程退出分支线程也退出。要在start前设置,通常不和join一起使用 代码示例
"""
线程属性
"""
from threading import Thread
from time import sleep
def func():
sleep(3)
print("线程属性测试")
t = Thread(target=func, name='よう')
t.setDaemon(True)
t.start()
print("name:", t.getName())
t.setName('Hello')
print("name:", t.getName())
print("is alive:", t.is_alive())
print("daemon:", t.isDaemon())
7.4 自定义线程类
7.4.1 创建步骤
- 继承Thread类
- 重写__init__方法添加自己的属性,使用super加载父类属性
- 重写run方法
7.4.2 使用方法
- 实例化对象
- 调用start自动执行run方法
- 调用join回收线程
代码示例
"""
自定义线程类示例
"""
from threading import Thread
class ThreadClass(Thread):
def __init__(self, *args, **kwargs):
self.attr = args[0]
super().__init__()
def f1(self):
print("step1")
def f2(self):
print("step2")
def run(self):
self.f1()
self.f2()
t = ThreadClass('abc')
t.start()
t.join()
from threading import Thread
from time import sleep, ctime
class MyThread(Thread):
def __init__(self, target=None, args=(), kwargs=None):
super().__init__()
self.target = target
self.args = args
self.kwargs = kwargs
def run(self):
self.target(*self.args, **self.kwargs)
def player(sec, song):
for i in range(3):
print("Playing %s : %s" % (song, ctime()))
sleep(sec)
t = MyThread(target=player, args=(3,), kwargs={'song': '凉凉'})
t.start()
t.join()
7.5 线程间通信
7.5.1 通信方法
线程间使用全局变量进行通信
7.5.2 共享资源争夺
共享资源: 多个进程或者线程都可以操作的资源称为共享资源。对共享资源的操作代码段称为临界区 影响: 对共享资源的无序操作可能会带来数据的混乱,或者操作错误。此时往往需要同步互斥机制协调操作顺序。
7.5.3 同步互斥机制
同步: 同步是一种协作关系,为完成操作,多进程或者线程间形成一种协调,按照必要的步骤有序执行操作。
互斥: 互斥是一种制约关系,当一个进程或者线程占有资源时会进行加锁处理,此时其他进程或线程就无法操作该资源,直到解锁后才能操作。
7.5.4 线程同步互斥方法
- 线程Event
from threading import Event
e = Event()
e.wait([timeout])
e.set()
e.clear()
e.is_set()
- 线程锁
from threading import Lock
lock = Lock()
lock.acquire()
lock.release()
with lock:
...
...
代码示例
"""
thread_lock.py 线程锁
"""
from threading import Thread, Lock
a = b = 0
lock = Lock()
def value():
while True:
lock.acquire()
if a != b:
print("a = %d,b=%d" % (a, b))
lock.release()
t = Thread(target=value)
t.start()
while True:
with lock:
a += 1
b += 1
|