网络编程
今日内容概要
目录
- 今日内容概要
- 一、软件开发架构
- 二、网络编程前言
- 三、OSI七层协议
- 四、各种重点协议
- 五、socket模块
- 六、黏包现象及解决方案
- 七、并发编程理论之操作系统发展史
- 八、多道技术
- 九、进程理论及调度算法
- 十、同步与异步
- 十一、阻塞与非阻塞
- 十二、创建进程的多种方式
- 十三、进程间数据隔离
- 十四、进程的join方法
- 十五、进程间通信之IPC机制
- 十六、进程对象诸多方法
- 十七、生产者消费者模型
- 十八、守护进程
- 十九、僵尸进程与孤儿进程
- 二十、多进程数据错乱问题
- 二十一、多进程实现TCP服务端并发
- 二十二、互斥锁代码实操
- 二十三、线程理论及创建线程的两种方式
- 二十四、GIL全局解释器锁
- 二十五、python多线程是否有用
- 二十六、锁死现象、信号量、event事件
- 二十七、进程池与线程池
- 二十八、协程及协程实现并发
- 软件开发架构
- 网络编程前言
- OSI七层协议
- 各种重点协议
- socket模块*
- 黏包现象及解决方案
- 并发编程理论之操作系统发展史
- 多道技术
- 进程理论及调度算法
- 同步与异步
- 阻塞与非阻塞
- 创建进程的多种方式
- 进程间数据隔离
- 进程join方法
- 进程间通信之IPC机制
- 进程对象诸多方法
- 生产者消费者模型
- 守护进程
- 僵尸进程与孤儿进程
- 多进程数据错乱问题
- 多进程实现TCP服务端并发
- 互斥锁代码实操
- 线程理论及创建线程的两种方式
- GIL全局解释器锁
- python多线程是否有用
- 锁死现象、信号量、event事件
- 进程池与线程池
- 协程及协程实现并发
一、软件开发架构
1.三层架构
展示层、核心逻辑层、数据处理层
2.C/S架构
C:client 客户端 S:server 服务端
3.B/S架构
B:browser 浏览器 S:server 服务器
"""
C/S架构和B/S架构其实都是三层架构的精炼、
把核心逻辑和数据处理合为一体了
演变出来了客户端和服务端
"""
#软件设计的大方向>>>:统一接口
#微信、支付宝的小程序就算典型例子
C/S架构
B/S架构
二、网络编程前言
1.什么是网络编程
基于网络编写代码 能够实现数据的远程交互
2.学习网络编程的目的
能够开发cs架构的软件
3.网络编程的起源
"""
最早起源于美国军事领域
想实现计算机之间数据的交互
最早的时候只能用硬盘拷贝
之后发明了网络编程
"""
4.网络编程必备条件
数据的远程交互
1.早期的电话
电话线
2.早期的大屁股电脑
网线
3.笔记本电脑、移动电话
网卡
ps:实现数据的远程交互必备的基础条件是物理连接介质
#网络相关的专业名词
1.交换机
能够将所有接入交换机的计算机彼此互联起来
2.广播
首次查找接入同一个交换机的其他计算机 需要朝交换机里面吼一嗓子
3.单播
首次被查找的计算机回应查找它的计算机 并附带自己的mac地址
4.广播风暴
接入同一台交换机的多台计算机同时发广播
5.局域网
可以简单的理解为有单个交换机组成的网络
在局域网内可以直接使用mac地址通信
6.广域网
可以简单的理解为范围更大的局域网
7.互联网
由所有的局域网、广域网连接到一起形成的网络
8.路由器
不同的局域网计算机之间是无法直接实现数据交互的 需要路由器连接
三、OSI七层协议
应用层:
表示层:
会话层:
传输层:用来标识一台计算机上面的某一个应用程序'PORT协议'
网络层:规定了所有接入互联网的计算机都必须有一个IP地址'IP协议'
数据链路层:规定了电信号的分组方式,'以太网协议'
物理连接层:主要用于确保计算机之间的物理连接介质 接收数据(bytes类型、二进制)
ps:应、表、会、传、网、数、物
"""
OSI七层协议:规定了所有的计算机在远程数据交互的时候必须经过相同的处理流程、在制造过程中必须拥有相同的功能硬件
"""
#接收网络消息 数据由下往上传递 物>>>应
#发送网络消息 数据由上往下传递 应>>>物
四、各种重点协议
1.IP协议
规定了所有互联网的计算机都必须有一个IP地址
# mac地址是物理地址可以看成永远无法修改
# IP地址是动态分配的 不同的场所IP是不同的
IP地址特征
IPV4:点分十进制
0.0.0.0
255.255.255.255
IPV6:能够给地球上每一粒分一个IP地址
IP地址可以跨局域网传输
2.PORT(端口协议)
PORT协议(端口协议)
用来标识一台计算机上面的某一个应用程序
范围:0-65535
特征:动态分配(不是一成不变的)
建议:
0-1024系统默认需要使用
1024-8000常见软件的端口号
8000之后的
URL:统一资源定位符(网址)
网址本质是有IP和PORT组成的
IP:PORT(组成方式:冒号连接)
eg:114.55.205.139:80
3.以太网协议
规定了计算机在出厂的时候都必须有一块网卡
网卡上有串12位16进制的数据(前六位缠上编号后六位流水线号),该数字也被称之为以太网地址/MAC地址
4.TCP与UDP协议
1.TCP协议:(Transmission Control Protocol,传输控制协议)即可靠协议,给对方发信息之后会保留一个副本,直到对方回应消息收到了才会删除 否则一定时间内反复发送
'数据不容易丢失的原因:不是因为有双向通道,而是因为有反馈机制'
三次握手建链接
四次挥手断链接
2.UDP协议:(User Datagram Protocol,用户数据报协议)也可以称之为不可靠协议
早期的QQ使用的是纯生的UDP协议,现在自己添加了很多技术和功能
使用UDP的原因:简单粗暴 快捷方便 只要指定对方的地址就可以发消息了
# 两个协议如何快速分清楚呢?
"""
TCP我们可以看成是打电话:双方你侬我侬
UDP我们可以看成是发短信:只要发了就行 不管对方看不看
"""
五、socket模块
1.模块简介
socket模块(有插座的意思,行业术语:套接字)是python自带的内置模块,类似于操作系统 给丑陋复杂的接口提供了简单快捷的接口 如果我们需要编写基于网络进行数据交互的程序 意味着我们需要自己通过代码来控制我们之前所学习的OSI七层(很繁琐 很复杂 类似于我们自己编写操作系统)
#基于文件类型的套接字家族(单机):AF_UNIX
#基于网络类型的套接字家族(联网):AF_INET
2.代码实现
#服务端
import socket
# 1.产生一个socket对象并指定采用的通信版本和协议(TCP)
server = socket.socket() # 括号内不写参数 默认就是TCP协议
# 2.绑定一个固定的地址(服务端必备的条件)
server.bind(('127.0.0.1', 8080)) # # 127.0.0.1为本地回环地址 只有自己的电脑可以访问
# 3.设立一个半连接池
server.listen(5)
# 4.等待接待客人
sock, addr = server.accept() # return sock, addr 三次握手
print(sock, addr)
# 5.服务客人
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send('你说的对,一定要坚强起来'.encode('utf8'))
# 6.关闭双下通道
sock.close() # 四次挥手
# 7.关闭服务端
server.close() # 店倒闭了
#客户端
import socket
# 1.生成socket对象指定类型和协议
client = socket.socket()
# 2.通过服务端的地址链接服务端
client.connect(('127.0.0.1',8080)) # 第一个参数是本机回送地址,第二个是端口号
# 3.直接给服务端发送消息
client.send('希望你赶快好起来,因为别人都在等着你出丑'.encode('utf8'))
# 4.接受服务端发过来的消息
data = client.recv(1024)
print(data.decode('utf8'))
# 5.断开与服务端的链接
client.close()
》》》服务端和客户端逻辑对比如下
步骤 | server | client |
---|---|---|
1 | 产生对象并指定通信协议 | 产生对象并指定通信协议 client = socket.socket() |
2 | 绑定地址server.bind(('127.0.0.1', 8080)) | 链接服务端 client.connect(('127.0.0.1',8080)) |
3 | 设立半连接池server.listen(5) | 直接发送消息 client.send('你好 '.encode('utf8')) |
4 | 等待接客人sock, addr = server.accept() | 接受服务端的信息 |
5 | 服务客人 | 断开链接client.close() |
6 | 关闭双向通道sock.close() | 无 |
7 | 关闭服务端server.close() | 无 |
3.代码优化
1.聊天内容自定义
#针对消息采用input获取
2.让聊天循环起来
#将聊天的部分用while循环包起来
3.用户输入的消息不能为空
#本质其实是两边不能都是recv或者send 一定是一方收一方发
4.服务端多次重启可能会报错
#Address already in use 主要是mac电脑会报
#方式1:改端口号
#方式2:博客里面代码拷贝即可
5.当客户端异常断开的情况下 如何让服务端继续服务其他客人
#windows服务端会直接报错
#mac服务端会有一段时间反复接收空消息延迟报错
#异常处理、空消息判断
六、黏包现象及解决方案
1.什么是黏包现象?
服务端一次性收到客户端发来的三次消息,本应该是一条一条的消息结果一起一次性收到了。
2.黏包现象出现的原因是什么?
不知道每次的数据到底有多大
TCP也称为流式协议也就是说数据绵绵不绝
3.如何解决该现象?
如果能解决以下两个问题黏包问题也会随之解决的
如何明确数据即将要接受的数据有多大
如何将长度变化的数据全部制作成固定长度的数据
struct模块
'''终极解决方案:字典作为报头打包 效果更好 数字更小'''
data_dict = {
'file_name': 'xxx老师教学.avi',
'file_size': 1231321312376767678686889,
'file_info': '内容很精彩 千万不要错过',
'file_desc': '一代神作 私人珍藏'
}
import json
data_json = json.dumps(data_dict)
print(len(data_json.encode('utf8'))) # 真实字典的长度 228
res = struct.pack('i', len(data_json.encode('utf8')))
print(len(res))
"""
黏包问题终极方案
客户端
1.制作真实数据的信息字典(数据长度、数据简介、数据名称)
2.利用struct模块制作字典的报头
3.发送固定长度的报头(解析出来是字典的长度)
4.发送字典数据
5.发送真实数据
服务端
1.接收固定长度的字典报头
2.解析出字典的长度并接收
3.通过字典获取到真实数据的各项信息
4.接收真实数据长度
"""
黏包代码实现
#服务端
import socket
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 8081))
server.listen(5)
sock, addr = server.accept()
# 1.接收固定长度的字典报头
data_dict_head = sock.recv(4)
# 2.根据报头解析出字典数据的长度
data_dict_len = struct.unpack('i', data_dict_head)[0]
# 3.接收字典数据
data_dict_bytes = sock.recv(data_dict_len)
data_dict = json.loads(data_dict_bytes) # 自动解码再反序列化
# 4.获取真实数据的各项信息
# total_size = data_dict.get('file_size')
# with open(data_dict.get('file_name'), 'wb') as f:
# f.write(sock.recv(total_size))
'''接收真实数据的时候 如果数据量非常大 recv括号内直接填写该数据量 不太合适 我们可以每次接收一点点 反正知道总长度'''
# total_size = data_dict.get('file_size')
# recv_size = 0
# with open(data_dict.get('file_name'), 'wb') as f:
# while recv_size < total_size:
# data = sock.recv(1024)
# f.write(data)
# recv_size += len(data)
# print(recv_size)
#客户端
import socket
import os
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1', 8081))
'''任何文件都是下列思路 图片 视频 文本 ...'''
# 1.获取真实数据大小
file_size = os.path.getsize(r'/Users/jiboyuan/PycharmProjects/day36/xx老师合集.txt')
# 2.制作真实数据的字典数据
data_dict = {
'file_name': '有你好看.txt',
'file_size': file_size,
'file_desc': '内容很长 准备好吃喝 我觉得营养快线挺好喝',
'file_info': '这是我的私人珍藏'
}
# 3.制作字典报头
data_dict_bytes = json.dumps(data_dict).encode('utf8')
data_dict_len = struct.pack('i', len(data_dict_bytes))
# 4.发送字典报头
client.send(data_dict_len) # 报头本身也是bytes类型 我们在看的时候用len长度是4
# 5.发送字典
client.send(data_dict_bytes)
# 6.最后发送真实数据
with open(r'/Users/jiboyuan/PycharmProjects/day36/xx老师合集.txt', 'rb') as f:
for line in f: # 一行行发送 和直接一起发效果一样 因为TCP流式协议的特性
client.send(line)
import time
time.sleep(10)
七、并发编程理论之操作系统发展史
#操作系统的发展史
1.穿孔卡片阶段
当时的计算机很庞大 使用分麻烦 一次只能一个人使用,程序员独占计算机 为所欲为;因此利用率太低
2.联机批处理系统
提前使用磁带一次性录入多个程序员编写的程序,然后交给计算机执行 CPU的效率有所提升,不用反复等待程序录入
3.脱机批处理系统
极大提升了CPU的利用率
'''操作系统的发展其实就是提升CPU利用率的过程'''
八、多道技术
'''一台计算机默认情况下,只有一个CPU'''
单道技术:所有的程序排队执行 过程中不能重合
多道技术:利用空闲时间提前准备其他数据 最大化提升CPU的利用率
1.切换(计算机的CPU在两种情况下会切换)
程序有IO操作和程序长时间占用CPU
2.保存状态
CPU每次切换走之前都需要保存当前操作的状态 下次切换回来基于上次的进度记录继续执行
九、进程理论及调度算法
1.进程理论
进程与程序的区别
进程:正在运行的程序
程序:一堆死代码
进程的调度算法
1.FCFS(先来先服务):对短作业不友好
2.短作业优先调度:对长作业不友好
3.时间片轮转法+多级反馈队列(目前还在用)
将时间均分 然后根据进程时间长短再分多个等级
等级越靠下表示耗时越长 每次分到的时间也会越多 但是优先级也会越低
2.进程的并行与并发
# 并行
多个进程同时执行 必须要有多个CPU参与 单个CPU无法实现并行
# 并发
多个进程看上去像同时执行 单个CPU可以实现 多个CPU也肯定可以
3.进程的三个状态
# 就绪态
所有的进程在被CPU执行之前都必须进入就绪态等待
# 运行态
CPU正在执行
# 阻塞态
进程运行过程中出现了IO操作 阻塞态无法进入运行态 需要先进入就绪态
十、同步与异步
# 用来表达任务的提交方式
同步:提交完任务之后原地等待任务的返回结果 期间不做任何事情
异步:提交完任务之后不会原地等待任务的返回结构 直接去做其他事情 有结果自动通知
十一、阻塞与非阻塞
1.用来表达任务的执行状态
阻塞:阻塞态
非阻塞:就绪态 运行态
2.综合使用
'''
同步阻塞
同步非阻塞
异步阻塞
异步非阻塞>>>效率最高
'''
十二、创建进程的多种方式
"""
1.鼠标双击软件图标
2.python代码创建进程
"""
# from multiprocessing import Process
# import time
#
#
# def task(name):
# print('task is running',name)
# time.sleep(3)
# print('task is over',name)
"""
在不同的操作系统中创建进程底层原理不一样
windows
以导入模块的形式创建进程
linux/mac
以拷贝代码的形式创建进程
"""
# if __name__ == '__main__':
# # p1 = Process(target=task, args=('jason',)) # 位置参数
# p1 = Process(target=task, kwargs={'name':'jason123'}) # 关键字参数
# p1.start() # 异步 告诉操作系统创建一个新的进程 并在该进程中执行task函数
# # task() # 同步
# print('主')
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self, name, age):
super().__init__()
self.name = name
self.age = age
def run(self):
print('run is running', self.name, self.age)
time.sleep(3)
print('run is over', self.name, self.age)
if __name__ == '__main__':
obj = MyProcess('jason', 123)
obj.start()
print('主')
十三、进程间数据隔离
同一台计算机上的多个进程数据是严格意义上的物理隔离(默认情况下)
from multiprocessing import Process
import time
money = 1000
def task():
global money
money = 666
print('子进程的task函数查看money', money)
if __name__ == '__main__':
p1 = Process(target=task)
p1.start() # 创建子进程
time.sleep(3) # 主进程代码等待3秒
print(money) # 主进程代码打印money
十四、进程的join方法
from multiprocessing import Process
import time
def task(name, n):
print('%s is running' % name)
time.sleep(n)
print('%s is over' % name)
if __name__ == '__main__':
p1 = Process(target=task, args=('jason1', 1))
p2 = Process(target=task, args=('jason2', 2))
p3 = Process(target=task, args=('jason3', 3))
# p.start() # 异步
'''主进程代码等待子进程代码运行结束再执行'''
# p.join()
# print('主')
start_time = time.time()
p1.start()
p1.join()
p2.start()
p2.join()
p3.start()
p3.join()
# p1.join()
# p2.join()
# p3.join()
print(time.time() - start_time) # 3秒多
十五、进程间通信之IPC机制
IPC:进程间通信
消息队列:存储数据的地方 所有人都可以存 也都可以取
from multiprocessing import Queue
q = Queue(3) # 括号内可以指定存储数据的个数
# 往消息队列中存放数据
q.put(111)
# print(q.full()) # 判断队列是否已满
q.put(222)
q.put(333)
# print(q.full()) # 判断队列是否已满
# 从消息队列中取出数据
print(q.get())
print(q.get())
# print(q.empty()) # 判断队列是否为空
print(q.get())
# print(q.empty()) # 判断队列是否为空
# print(q.get())
print(q.get_nowait())
"""
full() empty() 在多进程中都不能使用!!!
"""
from multiprocessing import Process, Queue
def product(q):
q.put('子进程p添加的数据')
def consumer(q):
print('子进程获取队列中的数据', q.get())
if __name__ == '__main__':
q = Queue()
# 主进程往队列中添加数据
# q.put('我是主进程添加的数据')
p1 = Process(target=consumer, args=(q,))
p2 = Process(target=product, args=(q,))
p1.start()
p2.start()
print('主')
十六、进程对象诸多方法
1.如何查看进程号
from multiprocessing import Process, current_process
current_process()
current_process().pid
import os
os.getpid()
os.getppid()
2.终止进程
p1.terminate()
ps:计算机操作系统都有对应的命令可以直接杀死进程
3.判断进程是否存活
p1.is_alive()
4.start()
5.join()
十七、生产者消费者模型
生产者
负责产生数据的'人'
消费者
负责处理数据的'人'
该模型除了有生产者和消费者之外还必须有消息队列(只要是能够提供数据保存服务和提取服务的理论上都可以)
十八、守护进程
守护进程会随着守护的进程结束而立刻结束
eg: 吴勇是张红的守护进程 一旦张红嗝屁了 吴勇立刻嗝屁
from multiprocessing import Process
import time
def task(name):
print('德邦总管:%s' % name)
time.sleep(3)
print('德邦总管:%s' % name)
if __name__ == '__main__':
p1 = Process(target=task, args=('大张红',))
p1.daemon = True
p1.start()
time.sleep(1)
print('恕瑞玛皇帝:小吴勇嗝屁了')
十九、僵尸进程与孤儿进程
僵尸进程
进程执行完毕后并不会立刻销毁所有的数据 会有一些信息短暂保留下来
比如进程号、进程执行时间、进程消耗功率等给父进程查看
ps:所有的进程都会变成僵尸进程
孤儿进程
子进程正常运行 父进程意外死亡 操作系统针对孤儿进程会派遣福利院管理
二十、多进程数据错乱问题
模拟抢票软件
from multiprocessing import Process
import time
import json
import random
# 查票
def search(name):
with open(r'data.json', 'r', encoding='utf8') as f:
data = json.load(f)
print('%s在查票 当前余票为:%s' % (name, data.get('ticket_num')))
# 买票
def buy(name):
# 再次确认票
with open(r'data.json', 'r', encoding='utf8') as f:
data = json.load(f)
# 模拟网络延迟
time.sleep(random.randint(1, 3))
# 判断是否有票 有就买
if data.get('ticket_num') > 0:
data['ticket_num'] -= 1
with open(r'data.json', 'w', encoding='utf8') as f:
json.dump(data, f)
print('%s买票成功' % name)
else:
print('%s很倒霉 没有抢到票' % name)
def run(name):
search(name)
buy(name)
if __name__ == '__main__':
for i in range(10):
p = Process(target=run, args=('用户%s'%i, ))
p.start()
"""
多进程操作数据很可能会造成数据错乱>>>:互斥锁
互斥锁:将并发变成串行 牺牲了效率但是保障了数据的安全
"""
二十一、多进程实现TCP服务端并发
import socket
from multiprocessing import Process
def get_server():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
return server
def get_talk(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
if __name__ == '__main__':
server = get_server()
while True:
sock, addr = server.accept()
# 开设多进程去聊天
p = Process(target=get_talk, args=(sock,))
p.start()
二十二、互斥锁代码实操
锁:建议只加载操作数据的部分 否则整个程序的效率会极低
from multiprocessing import Process, Lock
import time
import json
import random
def search(name):
with open(r'data.json', 'r', encoding='utf8') as f:
data = json.load(f)
print('%s查看票 目前剩余:%s' % (name, data.get('ticket_num')))
def buy(name):
# 先查询票数
with open(r'data.json', 'r', encoding='utf8') as f:
data = json.load(f)
# 模拟网络延迟
time.sleep(random.randint(1, 3))
# 买票
if data.get('ticket_num') > 0:
with open(r'data.json', 'w', encoding='utf8') as f:
data['ticket_num'] -= 1
json.dump(data, f)
print('%s 买票成功' % name)
else:
print('%s 买票失败 非常可怜 没车回去了!!!' % name)
def run(name, mutex):
search(name)
mutex.acquire() # 抢锁
buy(name)
mutex.release() # 释放锁
if __name__ == '__main__':
mutex = Lock() # 产生一把锁
for i in range(10):
p = Process(target=run, args=('用户%s号' % i, mutex))
p.start()
"""
锁有很多种 但是作用都一样
行锁 表锁 ...
"""
二十三、线程理论及创建线程的两种方式
1.线程理论
进程
进程其实是资源单位 表示一块内存空间
线程
线程才是执行单位 表示真正的代码指令
我们可以将进程比喻是车间 线程是车间里面的流水线
一个进程内部至少含有一个线程
1.一个进程内可以开设多个线程
2.同一个进程下的多个线程数据是共享的
3.创建进程与线程的区别
创建进程的消耗要远远大于线程
2.创建线程的两种方式
from threading import Thread
from multiprocessing import Process
import time
# def task(name):
# print(f'{name} is running')
# time.sleep(0.1)
# print(f'{name} is over')
#
# if __name__ == '__main__':
# start_time = time.time()
# p_list = []
# for i in range(100):
# p = Process(target=task, args=('用户%s'%i,))
# p.start()
# p_list.append(p)
# for p in p_list:
# p.join()
# print(time.time() - start_time)
# t_list = []
# for i in range(100):
# t = Thread(target=task, args=('用户%s'%i,))
# t.start()
# t_list.append(t)
# for t in t_list:
# t.join()
# print(time.time() - start_time)
# t = Thread(target=task, args=('jason',))
# t.start()
# print('主线程')
"""
创建线程无需考虑反复执行的问题
"""
class MyThread(Thread):
def run(self):
print('run is running')
time.sleep(1)
print('run is over')
obj = MyThread()
obj.start()
print('主线程')
3.线程的诸多特性
1.join方法
2.同进程内多个线程数据共享
3.current_thread()
4.active_count()
二十四、GIL全局解释器锁
# 官方文档对GIL的解释
In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.
"""
1.在CPython解释器中存在全局解释器锁简称GIL
python解释器有很多类型
CPython JPython PyPython (常用的是CPython解释器)
2.GIL本质也是一把互斥锁 用来阻止同一个进程内多个线程同时执行(重要)
3.GIL的存在是因为CPython解释器中内存管理不是线程安全的(垃圾回收机制)
垃圾回收机制
引用计数、标记清除、分代回收
"""
# 验证GIL的存在
from threading import Thread
num = 100
def task():
global num
num -= 1
t_list = []
for i in range(100):
t = Thread(target=task)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(num)
# GIL与普通互斥锁
既然CPython解释器中有GIL 那么我们以后写代码是不是就不需要操作锁了!!!
"""
GIL只能够确保同进程内多线程数据不会被垃圾回收机制弄乱
并不能确保程序里面的数据是否安全
"""
import time
from threading import Thread,Lock
num = 100
def task(mutex):
global num
mutex.acquire()
count = num
time.sleep(0.1)
num = count - 1
mutex.release()
mutex = Lock()
t_list = []
for i in range(100):
t = Thread(target=task,args=(mutex,))
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(num)
二十五、python多线程是否有用
需要分情况
情况1
单个CPU
多个CPU
情况2
IO密集型(代码有IO操作)
计算密集型(代码没有IO)
1.单个CPU
IO密集型
多进程
申请额外的空间 消耗更多的资源
多线程
消耗资源相对较少 通过多道技术
ps:多线程有优势!!!
计算密集型
多进程
申请额外的空间 消耗更多的资源(总耗时+申请空间+拷贝代码+切换)
多线程
消耗资源相对较少 通过多道技术(总耗时+切换)
ps:多线程有优势!!!
2.多个CPU
IO密集型
多进程
总耗时(单个进程的耗时+IO+申请空间+拷贝代码)
多线程
总耗时(单个进程的耗时+IO)
ps:多线程有优势!!!
计算密集型
多进程
总耗时(单个进程的耗时)
多线程
总耗时(多个进程的综合)
ps:多进程完胜!!!
from threading import Thread
from multiprocessing import Process
import os
import time
def work():
# 计算密集型
res = 1
for i in range(1, 100000):
res *= i
if __name__ == '__main__':
# print(os.cpu_count()) # 12 查看当前计算机CPU个数
start_time = time.time()
# p_list = []
# for i in range(12): # 一次性创建12个进程
# p = Process(target=work)
# p.start()
# p_list.append(p)
# for p in p_list: # 确保所有的进程全部运行完毕
# p.join()
t_list = []
for i in range(12):
t = Thread(target=work)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print('总耗时:%s' % (time.time() - start_time)) # 获取总的耗时
"""
计算密集型
多进程:5.665567398071289
多线程:30.233906745910645
"""
def work():
time.sleep(2) # 模拟纯IO操作
if __name__ == '__main__':
start_time = time.time()
# t_list = []
# for i in range(100):
# t = Thread(target=work)
# t.start()
# for t in t_list:
# t.join()
p_list = []
for i in range(100):
p = Process(target=work)
p.start()
for p in p_list:
p.join()
print('总耗时:%s' % (time.time() - start_time))
"""
IO密集型
多线程:0.0149583816528320
多进程:0.6402878761291504
"""
二十六、锁死现象、信号量、event事件
1.锁死现象
acquire()
release()
from threading import Thread,Lock
import time
mutexA = Lock() # 产生一把锁
mutexB = Lock() # 产生一把锁
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexB.acquire()
print(f'{self.name}抢到了B锁')
mutexB.release()
print(f'{self.name}释放了B锁')
mutexA.release()
print(f'{self.name}释放了A锁')
def func2(self):
mutexB.acquire()
print(f'{self.name}抢到了B锁')
time.sleep(1)
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexA.release()
print(f'{self.name}释放了A锁')
mutexB.release()
print(f'{self.name}释放了B锁')
for i in range(10):
obj = MyThread()
obj.start()
2.信号量
acquire()
release()
from threading import Thread,Lock
import time
mutexA = Lock() # 产生一把锁
mutexB = Lock() # 产生一把锁
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexB.acquire()
print(f'{self.name}抢到了B锁')
mutexB.release()
print(f'{self.name}释放了B锁')
mutexA.release()
print(f'{self.name}释放了A锁')
def func2(self):
mutexB.acquire()
print(f'{self.name}抢到了B锁')
time.sleep(1)
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexA.release()
print(f'{self.name}释放了A锁')
mutexB.release()
print(f'{self.name}释放了B锁')
for i in range(10):
obj = MyThread()
obj.start()
3.event事件
from threading import Thread, Event
import time
event = Event() # 类似于造了一个红绿灯
def light():
print('红灯亮着的 所有人都不能动')
time.sleep(3)
print('绿灯亮了 油门踩到底 给我冲!!!')
event.set()
def car(name):
print('%s正在等红灯' % name)
event.wait()
print('%s加油门 飙车了' % name)
t = Thread(target=light)
t.start()
for i in range(20):
t = Thread(target=car, args=('熊猫PRO%s' % i,))
t.start()
二十七、进程池与线程池
进程和线程能否无限制的创建 不可以
因为硬件的发展赶不上软件 有物理极限 如果我们在编写代码的过程中无限制的创建进程或者线程可能会导致计算机奔溃
池
降低程序的执行效率 但是保证了计算机硬件的安全
进程池
提前创建好固定数量的进程供后续程序的调用 超出则等待
线程池
提前创建好固定数量的线程供后续程序的调用 超出则等待
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import os
import time
import random
from threading import current_thread
# 1.产生含有固定数量线程的线程池
# pool = ThreadPoolExecutor(10)
pool = ProcessPoolExecutor(5)
def task(n):
print('task is running')
# time.sleep(random.randint(1, 3))
# print('task is over', n, current_thread().name)
# print('task is over', os.getpid())
return '我是task函数的返回值'
def func(*args, **kwargs):
print('from func')
if __name__ == '__main__':
# 2.将任务提交给线程池即可
for i in range(20):
# res = pool.submit(task, 123) # 朝线程池提交任务
# print(res.result()) # 不能直接获取
# pool.submit(task, 123).add_done_callback(func)
二十八、协程及协程实现并发
1.协程
"""
进程:资源单位
线程:执行单位
协程:单线程下实现并发(效率极高)
在代码层面欺骗CPU 让CPU觉得我们的代码里面没有IO操作
实际上IO操作被我们自己写的代码检测 一旦有 立刻让代码执行别的
(该技术完全是程序员自己弄出来的 名字也是程序员自己起的)
核心:自己写代码完成切换+保存状态
"""
import time
from gevent import monkey;
monkey.patch_all() # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn
def func1():
print('func1 running')
time.sleep(3)
print('func1 over')
def func2():
print('func2 running')
time.sleep(5)
print('func2 over')
if __name__ == '__main__':
start_time = time.time()
# func1()
# func2()
s1 = spawn(func1) # 检测代码 一旦有IO自动切换(执行没有io的操作 变向的等待io结束)
s2 = spawn(func2)
s1.join()
s2.join()
print(time.time() - start_time) # 8.01237154006958 协程 5.015487432479858
2.协程实现并发
import socket
from gevent import monkey;monkey.patch_all() # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn
def communication(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
def get_server():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
sock, addr = server.accept() # IO操作
spawn(communication, sock)
s1 = spawn(get_server)
s1.join()
如何不断的提升程序的运行效率
多进程下开多线程 多线程下开协程