网络并发
周总结
计算机网络编程理论
针对软件开发 目前市面上的主流软件 都基本上会使用网络进行通信 传输各种数据作为软件开发人员 掌握网络编程理论和了解网络编程开发架构是必须的
软件开发架构
ATM 和选课系统都是用了三层架构的方式 第一层是用户层 就是客服端 第二层和第三层 是接口层和数据处理层 可以看成是服务端
C/S架构
Client:客户端
eg:计算机上面下载的各种app
Server:服务端
软件的核心数据都在他们的服务端中 核心的处理数据的方式也都在服务端的程序中
B/S架构
Browser:浏览器
eg:浏览器可以直接访问的网页版的淘宝
Server:服务端
''' 浏览器可以充当所有服务端的客户端'''
C/S架构和B/S架构的区别:
C/S架构:
优势:是不同的互联网公司独立开发的 可以高度定制客户端功能
劣势:需要下载才能使用
B/S架构:
优势:不用下载可以直接使用
劣势:不可以高度定制客户端 并且要遵循很多规则
计算机网络专业名词
工作方式
边缘部分:用户直接使用C/S等方式通信
核心部分:为用户服务 包括网络通路 交换机 路由器等等设备
交换机
能够将所有接入交换机的计算机彼此连接起来
广播
当某服务器首次想要与某一台服务器连接时 就会向每一台服务器发送相同的请求 不管你遇需不需要
单播
首次被查找的服务器会回应查找他的计算机 会把自己的mac地址也传过去
广播风暴
接入同一台交换机的多台计算机同时发广播
局域网
是指在某一区域内由多台计算机互联成的计算机组 可以使用mac地址通信
广域网
范围更大的广域网
互联网
由所局域网和广域网连接到一起形成的网络
网络编程协议简介
OSI七层协议
规定了所有计算机再进行交互的时候都必须经过相同的处理流程 在制造过程中必须拥有相同的功能硬件
1.应用层
2.表示层
3.会话层
4.传输层
5.网络层
6.数据链路层
7.物理连接层
在进行发送数据的时候都会进行打包 每一层接收到的就是上一层打包的总和
在接收数据的时候会进行解包 每一层解包都会更加还原原本数据
五层协议
应用层 表示层 会话层 可以都看成应用层
OSI七层协议之物理层
物理层要确保计算机之间的物理连接介质 接收到的数据(bytes类型)
OSI七层协议之数据链路层
1.规定了电信号的分组方式
2.以太网协议
规定了计算机在出厂的时候有一块网卡 就是他的身份证 且独一无二一直不会改变
数字特征:12位16进制数 前6位是厂商编号 后6位是流水线号
以太网也被称为MAC地址
OSI七层协议之网络层
IP协议:规定了所有接入互联网的计算机都必须有一个ip地址 类似于身份证号
mac地址是永远无法改变
ip地址是动态分配的 不同的场所ip地址不同
ip地址分为:
IPV4:点分十进制
0.0.0.0
225.225.225.225
IPV6:很多
ps:ip地址可以标识世界上独一无二的一台计算机
OSI七层协议之传输层
PORT协议
用来表示一台计算机上的某一个应用程序
范围0-65535
特征:也是动态分配 但是一些端口会固定分配给一些常见的软件
为了避免冲突我们所写的软件端口号最好是8000以后
0-1024 系统默认使用的一些端口号
1024-8000 常见软件的端口号
8000 不常见的
URL:统一资源定位符(网址)
网址的本质:IP:PORT (之间用:连接)
网址能够定位世界上独一无二的一台计算机上面的某一个应用程序
域名解析:可以将往之解析成ip+port
网络编程之socke模块
传输层之TCP与UDP协议
TUP和UDP都是用来规定通信方式的
TCP协议
又称可靠协议(数据不容易丢失)
三次握手
客户端发送连接请求 服务端接收到了并反馈 可以接受 建立从客服端到服务端的通道 与此同时 服务端也会给客户端发送一个连接请求 让客户端同意服务端向客服端建立通道 这样就建立了双向通道
造成数据不易流失的原因不是因为有通道 而是因为有反馈机制
在向对方发送消息的时候 自己会保留一个副本 直到对方回应 我收到消息了 才会删除本地数据 否则会在一定时间内反复发送
洪水攻击
同一时间有大量客户端请求建立连接 会导致服务端一致处于SYN_RCVD状态
服务端如何区分客服端建立连接的请求
可以对请求做唯一表示、如他们的ip地址
四次挥手
想要关闭应用程序 客户端向服务端发送关闭程序请求 客户端回应可以关闭 那么客服端与服务端连接的通道就会关闭 但是 服务端与客户端的通道仍然存在
一段时间以后 服务端也会向客户端发送关闭请求 客户端同意 通道断开
TIME WAIT在这个期间 服务端仍然可以向客服端发送数据
UDP协议
也成为不可靠协议 没有反馈机制 一段发送消息以后就不用惯了 不用等到对方回复就可以发送第二次消息
socket模块
socket是应用层与TCP/IP协议族通信的中间全歼抽象层 它是一组接口 在设计模式中 socket其实就是一个门面模式 他把复杂的TCP/IP协议族隐藏在sockrt接口后面 对用户来说 一组简单的接口就是全部 让socket去组织数据 以符合指定的协议
简而言之就是 socket帮我们把传输层及以下的处理全做了 调这个模块就能帮助我们通过网络完成两个程序之间的通讯
socket模块方法总结
socket():产生一个socket对象并指定采用的通信版本和协议
bind((ip,port)):绑定一个固定的ip和端口号 服务端必备
listen()设立半连接池 容量为5
connect((ip,port)):向服务端发送链接请求 并接受握手回馈信号(三次握手)
accept():等待客户端的连接请求 并发出握手回馈信号 (三次握手)
返回一个双向通道来send和recv 因为只有这个通道有客户端的地址信息
send(二进制数据):可以让建立协议的两端互相发送消息
recv(字节数):可以让建立协议的两端互相接收消息
代码优化
1.聊天内容自定义
针对消息采用input获取
2.让聊天循环起来
将聊天的部分用循环包起来
3.用户输入的信息不能为空
本质其实就是两边不能都是recv或者cend 一定是一方收一方发
4.服务端多次重启可能会报错
Address already in use 主要是mac电脑汇报
方式1:改端口号
方式2:博客里面拷贝代码
5.当客户端异常断开的情况下 如何让服务端继续服务其他客人
windows服务端会直接报错
mac服务端会有一段时间反复接收空消息延迟报错
异常处理、空消息判断
半连接池
server.listen(5)
当有多个客户端来链接的情况下 我们可以设置等待数量(不考虑并发)
假设服务端只有一个人的情况下
在测试半连接池的时候 可以不用input获取消息 直接把消息写死即可
TCP协议的黏包现象
黏包现象
当我们从客户端连续发送send内容 从执行recv来接收时 会发现 应该分三次发来的消息 全部被recv一次性接受了 并且黏在了一起 这就是粘包现象
粘包现象的原因
流式协议
流式协议就像水一样传输 没有间隔
实际上 TCP 协议会针对数据量较小 且发送时间间隔较短的多条数据一次性合并打包发送
避免黏包现象的核心思路
如何明确即将接收的数据具体有多大
黏包解决方案
可以在传输之前先确定文件的大小 这样接受放就知道在核实停止接收
需要用struct模块
struct模块:报头
文件大小可以通过struct模块来固定长度 将这个固定长度有的数据发送给接收方 接收放可以提前准备
借助struct模块 我们知道长度数字可以转换成一个转准的4字节的数字(也有固定别的长度的) 因此可以利用这个特点来预先发送数据长度
发送时 | 接收时 |
---|---|
先发送struct转换好的数据长度4字节 | 先接受4个字节使用struct准换成数字来获取接收的数据长度 |
在发送数据 | 按照长度接收数据 |
TPC代码实现
服务端
import json
import socket
import struct
server = socket.socket()
server.bind(('127.0.0.1', 8888))
server.listen(5)
sock, addr = server.accept()
length = struct.unpack('i', sock.recv(4)) # 得到文件信息长度
file_infos = json.loads(sock.recv(length[0])) # 得到文件信息二进制并反序列化为数据
total_size = file_infos.get('file_size') # 拿到数据大小
with open('现在是我的学习资料', 'wb') as f:
recv_size = 0 # 初始接收了0字节的数据
while recv_size < total_size: # 当接收量不在小于文件总大小,则停止接收
data = sock.recv(1024) # 每次接收1024个字节
f.write(data) # 写入本地
recv_size += len(data) # 每次接收后,将接收到的数据量记录下来
# 客户端
import json
import os
import socket
import struct
client = socket.socket()
client.connect(('127.0.0.1', 8888))
file_size = os.path.getsize('学习资料')
file_infos = {
'file_name': '电脑配件',
'file_size': file_size,
}
bytes_infos = json.dumps(file_infos).encode('utf8') # 数据先变成json再编码为二进制以便还原
client.send(struct.pack('i', len(bytes_infos))) # 先发送文件信息的长度
client.send(bytes_infos) # 再发送文件信息
with open('学习资料', 'rb') as f:
for line in f: # 流式协议,所以分几次发都是粘着的(但是这样可以减少发送端的内存压力)
client.send(line) # 最终发送文件本身
UDP代码实现
# 服务端
import socket
server = socket.socket(type=socket.SOCK_DGRAM) # 建立了UDP协议的连接
server.bind(('127.0.0.1', 8080)) # 依然可以绑定某个端口号
while True:
data, addr = server.recvfrom(1024) # recvfrom接收udp协议的消息,返回信息和发送端的地址
print(f'来自{addr}的消息:{data.decode("utf8")}')
server.sendto('收到了'.encode('utf8'), addr) # sendto(信息,接收端地址)
# 客户端
import socket
conn = socket.socket(type=socket.SOCK_DGRAM) # 建立了UDP协议的连接
conn.sendto('我发送了个消息,你收到了吗'.encode(), ('127.0.0.1', 8080))
data, addr = conn.recvfrom(1024)
print(f'来自{addr}的回复:{data.decode("utf8")}')
操作系统的发展
1.穿孔卡片
计算机很庞大 使用很麻烦 一次只能给一个人使用 期间很多计算机都不工作
2.联机批处理系统
提前使用磁带一次性录入多个程序员编写的程序然后交给计算机执行
cpu工作效率提升 不用反复等到程序录入
3.脱机批处理系统
极大的提升了cpu的利用率
多道技术实现方式
1.切换
计算机的cup在两种情况下会切换(不让你用 给别人用)
1.程序有IO操作
输出/输入
input、time.sleep、read、write
2.程序长时间占用cup
我们的雨露均沾 让多个程序都能被cpu运行一下
2.保存状态
cpu每次切换走之前抖需要保存当前操作的状态 下次切换回来基于上次的进度继续执行
进程理论
进程与程序的区别
程序:一对死代码(还没有被运行起来)
进程:正在运行的程序(被运行起来了)
进程的调度算法
进程的调度算法指操作系统面对多个进程时 如何让分配cpu时间的策略
1.先来先服务的算法
简单的策略 哪个进程先进来 就先执行完哪个进程
缺点很明显 当遇到一个长时间的进程时 对短进程不友好
2.短作业优先调度法
也很粗暴 哪个进程执行的快就执行哪个进程 堆场进程又不友好
3.时间片轮转法
每次分配cpu的一个小时间段给某一个进程 到时间就保存当前进程状态和切 换另外一个进程
这样分配 所有程序都不会等太久
4.多级反馈队列
在时间片轮转的基础上 这个程序如果每次都是因为时间片到时间了而不是IO被剥夺使用权 那就每次分配长一点的时间给这个程序
这样可以减少轮转的频率 也可以额昂各种程序都执行的相对连贯
进程的三种状态
就绪态:
每当程序开始运行编程进程 都会进入这个状态 是等待cpu的使用权的状态
运行态:
正被cpu执行的状态
当程序结束会进入终止态
当程序遇到IO会进入运行态
当时间片到就会返回就绪态(cpu要分配给其他进程)
阻塞态:
正在执行IO操作时 这时cpu就不会去选择执行这段进程
当阻塞态结束会重新回到就绪态
创建态和终止态:进程开始和结束的状态
同步与非同步
在多进程并发中 我们认为 所有的今晨给都是有异步属性的 及当一个进程遇到IO等操作 这个进程不会进入到一个忙等待的状态(占用CPU却又等待IO的返回值)而是直接交出cpu的执行权给其他进程 让cpu去执行其他任务
同步就是指要等待IO或者其他的结果进程才会继续向前 类似于一个接一个的串行执行 大亨多个任务的在某种节点上的同步
阻塞与非阻塞
阻塞就是进程进入阻塞态 非阻塞就是程序处于就绪态和运行态
当一个任务因调用或IO要获取某个结果 阻塞调用必须等待调用结果 非阻塞调用则可以不必等待这个结果而使进程是中处于就绪态和运行态
异步非阻塞
当我们说一个程序是异步非阻塞的程序 实际上就是说这个程序很吃cpu的利用率 同时效率也是最高 因为这样的程序高并发 且有多个进程始终处于就绪态和运行态 所以cpu执行这个程序始终处于就绪态和运行态 所以cpu执行这个程序中的进程的时间就会比较多 执行速度就会很快 这种方式常应用于游戏行业
进程通信
进程之间的数据默认是隔离的 每个进程间的数据变量都存在于各自的名称空间中 不会互相影响
我们可以通过消息队列和文件操作等方式实现进程间的通信 让不同的进程能够对同一份数据进行操作
但是这样又可能出现数据错乱的问题
互斥锁
为了防止数据错乱的情况我们应该将操作数据变成一种同步状态 即当一个进程A操作数据时 另一个进程B就不应该操作这份数据了 而应该等着当前进程A处理完数据进程B再去操作这个数据 这样可以让我们的数据变得安全
lock.acquire() #进入区
读取文件中的数据进行操作 #临界区
lock.release() #退出区
进入区:所有需要操作数据的进程 都需要到这个区来等待数据的操 作权
当判断没有进程进入临界区的时候 就取就最先进入的进程 进入临界区
当判断已经有进程在临界区内 就要在进入区等待
临界区:同时只有一个进程可以进入临界区做一些处理数据的操作
退出区:是进程处理完数据 释放互斥锁的过程
代码创建进程的方式
同步与异步的区别
创建方式1:
from multiprocessing import Process
import time
def task(name):
print('task is running', name)
time.sleep(3)
print('task is over', name)
if __name__ == '__main__':
p1 = Process(target=task, args=('leethon',)) # 位置参数,用元组形式传
# p1 = Process(target=task, kwargs={name:'leethon',}) # 关键字参数,用字典形式
p1.start() # 异步 告诉操作系统创建一个新的进程,并在该进程中执行task函数
# task() # 同步
time.sleep(1)
print('主进程') # 当前进程继续执行它的程序
同步方式下 task和我们的主进程串行 必须等task执行完毕后 主进程才会执行:
# 同步运行结果:
task is running leethon
task is over leethon
主进程
异步方式下 task和我们的主进程并行 且大量时间处于阻塞态 所以可以看做task和主进程是并发的 同时运行的
# 异步运行结果
task is running leethon # task起
主进程 # 一秒后主进程打印
task is over leethon # 3秒后task结束
创建方式2:
from multiprocessing import Process
import time
class MyProcess(Process): # 继承Process类
def __init__(self, name, age): # 如果要传参,则通过这种方式,调用类名时触发
super().__init__() # 派生原本的方法
self.name = name # 创建完一个进程后,添加新的属性(或者导致修改原有属性)
self.age = age
def run(self): # 这就是方式1中的task,start时会运行其中的大爱吗
print('run is running', self.name, self.age)
time.sleep(3)
print('run is over', self.name, self.age)
if __name__ == '__main__':
obj = MyProcess('leethon', 18)
obj.start() # 启动子进程
time.sleep(1)
print('主进程')
# 运行结果
run is running leethon 18
主进程
run is over leethon 18
进程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__':
p = Process(target=task, args=('leethon1', 1))
p.start() # 异步
p.join() # 主进程代码等待子进程代码运行结束再执行
print('主')
子进程的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=('leethon1', 1))
p2 = Process(target=task, args=('leethon2', 2))
p3 = Process(target=task, args=('leethon3', 3))
start_time = time.time() # 标记开始时间
"""统计两种代码的运行时间"""
print(time.time() - start_time) # 得到总执行时间
##1 每启动一个进程后紧挨着join
p1.start()
p1.join()
p2.start()
p2.join()
p3.start()
p3.join()
"""运行结果:6s多,因为p1结束后才启动p2,相当于三个进程串行,所以一共1+2+3 = s"""
##2 先启动三个进程再最后统一join
p1.start()
p2.start()
p3.start()
p1.join()
p2.join()
p3.join()
"""运行结果:3s多,p1和p2、p3几乎同时启动,那么join结束后统计的时间就是取决于最长的那一个"""
进程间数据隔离
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) # 100
上述程序中 我们在子进程看似更改了全局变量的money实际上我们联系底层 task的进程是通过导入模块的方式启动的 所以其实他改动的是他所在的模块的全局money而不会影响主进程的任何数据
这就是进程间的数据隔离
进程间通讯—IPC机制
IPC就是跨进程通信 为了能使多进程协调工作而让彼此隔离的进程有沟通的能力
消息队列
存储数据的地方 所有的主进程和子进程都能对队列进行存取
基础使用
from multiprocessing import Queue
q = Queue(3) # 括号内可以指定存储数据的个数
# 往消息队列中存放数据
q.put(111)
print(q.full()) # 判断队列是否已满 # False
q.put(222)
q.put(333)
print(q.full()) # 判断队列是否已满 # True
# q.put(444) # 程序卡在这里
"""实际上put会等待队列有空位,如果队列为满,那就等有一个数据被取走后,再往队列里放"""
# 从消息队列中取出数据
print(q.get())
print(q.get())
# print(q.empty()) # 判断队列是否为空 # False
print(q.get())
# print(q.empty()) # 判断队列是否为空 # True
# 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() # <_MainProcess name='MainProcess' parent=None started
current_process().pid # 18792
import os # os 模块也可以查到
os.getpid() # 18792
os.getppid() # 2020 # 父进程的pid号
# 2.终止进程
p1.terminate()
'''
ps:计算机操作系统都有对应的命令可以直接杀死进程
如windows可以通过taskkill指令来杀死进程,通过help 指令名 可以查看win指令的详细用法
'''
3.判断进程是否存活
p1.is_alive()
守护进程
守护进程会随着守护的进程结束而立刻结束
代码中 提现为父进程结束后 那么开启的子进程也不会继续执行 直接结束
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=('leethon',))
p1.daemon = True # 放在子进程开启之前,表示守护主进程
p1.start()
time.sleep(1)
print('主进程结束')
# 运行结果
子进程开始:leethon
主进程结束
# 没有子进程结束的打印
僵尸进程和孤儿进程
僵尸进程
进程执行完毕后不会立刻销毁所有的数据 会有一些信息短暂的保留下来
比如:进程号、进程执行时间、进程消耗功率等给父进程看
ps:所有的进程都会编程僵尸进程
孤儿进程
子进程正常运行 父进程意外死亡 操作系统针对孤儿进程会派遣'福利院'
互斥锁代码实操
锁:建议只加载操作数据的部分 否则整个程序的效率会很低
def search(name):
with open(r'joyce.txt', 'r', encoding='utf8') as f:
data = json.load(f)
print('%s查看票 目前剩余:%s' % (name, data.get('ticket_num')))
def buy(name):
with open(r'joyce.txt', 'r', encoding='utf8') as f:
data = json.load(f)
# 模拟网络延迟 随机秒数
time.sleep(random.randint(1, 3))
# 买票
if data.get('ticket_num') > 0:
with open(r'joyce.txt', '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.一个线程内可以开设多个线程
2.同一个进程下的多个线程数据是共享的
3.创建进程与线程的区别
创建进程的消耗要远远大于线程
创建线程的两种方式
方式1:
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(10):
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(10):
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('主进程')
'''
创建线程无需考虑反复执行的问题
'''
方式2:
class MyThread(Thread):
def run(self):
print('run is running')
time.sleep(1)
print('run is over')
obj = MyThread()
obj.start()
print('主线程')
线程的诸多特性
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 Jpthon PYpython
2.GIL本质也是一把互斥锁 用来阻止同一个进程内多个线程同时执行(重要)
3.GIL的存在是因为Cpython解释器中内存管理不是线程安全的(垃圾回收机制)
垃圾回收机制(引用计数、标记清楚、分代回收)
验证GIL的存在
from threading import Thread
num = 100
def task():
global num
num -=1
t_list = []
for i in range(10):
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
"""
死锁现象
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()
信号量
在python并发编程中信号量相当于多把互斥锁(公共厕所)
from threading import Thread, Lock, Semaphore
import time
import random
sp = Semaphore(5) # 一次性产生五把锁
class MyThread(Thread):
def run(self):
sp.acquire()
print(self.name)
time.sleep(random.randint(1, 3))
sp.release()
for i in range(20):
t = MyThread()
t.start()
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)
协程
进程:资源单位
线程:执行单位
协程:单线程下实现并发(效率极高)
在代码层面欺骗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
携程实现并发
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()
如何不断的提升程序的运行效率
多进程下开多线程 多线程下开协程