网络编程
两个版本结合着看吧!
C/S架构和B/S架构
- C/S架构
- 基于客户端(client)和服务器(server)之间的通信.
- 优点:
- 响应速度快
- 安全性高
- 缺点:
- 开发成本高,需要编写服务端和客户端
- 维护成本高
- 占用空间
- B/S架构
- 基于浏览器(browse)和服务器之间的通信
- 优点:
- 开发成本低,只需要编写服务端
- 维护成本低
- 占用空间相对较低
- 缺点:
- 响应速度慢
- 安全性差
操作系统
- 什么是操作系统
- 操作系统是一个协调、管理和控制计算机硬件资源和软件资源的控制程序
- 操作系统的功能
- 隐藏丑陋的硬件调用接口,提供更好、更简单、更清晰的模型
- 合理控制程序使用硬件资源
- 多道技术
- 什么是多道技术
- 多道指的是多个程序,解决多个程序竞争一个资源的有序调度问题,解决方式为多路复用,即空间上的和复用和时间上的复用
- 空间上的复用
- 将内存分为几个部分,每个部分放入一个程序
- 时间上的复用
- 当一个程序遇到IO阻塞时,可以将cpu切换到另一个程序上
- 什么是多道技术
网络通信
- 通信的原理
- 通过物理连接介质连接
- 找到对方计算机的软件的位置
- 通过统一的互联网协议进行数据收发
osi七层协议
应用层 传输层 网络层 数据链路层 物理层
应用层
- 规定应用程序的数据格式
传输层
- 建立端口到端口见的通信
- tcp协议
- 特点
- 可靠传输
- 面向连接的流式协议
- 传输字节流
- 传输效率低
- 三次握手和四次挥手
- 三次握手
-
客户端向服务端请求建立连接通道
- 发送(syn=1 seq=x)
-
服务端同意请求并申请向客户端建立连接通道
- 发送(ack=1+x syn=1 seq=x)
-
客户端同意请求
- 发送(ack=1+x)
-
补充
- 第二次握手是将服务端同意请求和向客户端请求建立连接合并为一步,
- 建立通道是单方面通道
client发送了第一个连接的请求报文,但是由于网络不好,这个请求没有立即到达服务端,而是在某个网络节点中滞留了,直到某个时间才到达server。
本来这已经是一个失效的报文,但是server端接收到这个请求报文后,还是会想client发出确认的报文,表示同意连接。
假如不采用三次握手,那么只要server发出确认,新的建立就连接了,但其实这个请求是失效的请求,client是不会理睬server的确认信息,也不会向服务端发送确认的请求。但是server认为新的连接已经建立起来了,并一直等待client发来数据,这样,server的很多资源就没白白浪费掉了。
采用三次握手就是为了防止这种情况的发生,server会因为收不到确认的报文,就知道client并没有建立连接。这就是三次握手的作用。
-
- 四次挥手
- 客户端向服务端请求断开连接通道
- 发送(fin=1 seq=y)
- 服务端同意请求
- 发送(ack=1+y)
- 服务端向客户端请求断开连接通道
- 发送(fin=1 seq=y)
- 客户端同意请求
- 发送(ack=1+y)
- 补充,当客户端信息发送完毕,向提出断开连接请求时不能确保服务器是否也同样完成了发送,所以第二步和第三步不能合为一步
- 客户端向服务端请求断开连接通道
- syn洪水攻击:
- 制造大量的假的无效的IP请求服务器.致使正常的IP访问不了服务器.
- 三次握手
- 特点
- upd协议
- 特点
- 不可靠传输
- 面向报文(数据包)无连接的服务
- 传输效率高
- 特点
网络层
- 引入一套地址用来区分不同的广播域(子网),即网络地址
- IP协议
- ip协议就是规定网络地址的协议,
- ip地址
- ip协议定义的地址叫ip地址,格式采用四段十进制数
- ip地址的范围是0.0.0.0~255.255.255.255
- 子网掩码
- 通过子网掩码就可以判断任意两个ip地址是否处于同一个子网下
- 分类
- A类:255.0.0.0
- B类:255.255.0.0
- C类:255.255.255.0
- C类一个网段最多承载254个ip地址(0号和255号被占用)
- ARP协议
- 通过ip地址获取对方的mac地址
- 第一次会以广播方式发送,目标计算机接收到消息后将自己的mac地址一并回复
- 源mac 目标mac 源ip 目标ip 数据
数据链路层
- 对数据进行分组
- 以太网协议
- 统一的分组方式
- 以太网协议规定一组数据构成一个数据报,叫做帧,每一帧分为报头和数据两部分
- 报头head,长度为18个字节
- 源地址:6个字节
- 目标地址:6个字节
- 数据类型:6个字节
- 数据data,长度最短46字节,最长1500字节
- 一帧数据最少64个字节,最大1518个字节,超出限制就分片发送
- 报头head,长度为18个字节
- mac地址
- mac地址就是网卡的唯一标识
- 由12位16进制组成,前6位是厂商的编号,后六位是流水线号
- 交换机的mac地址学习功能
- 当所有接口都对应上具体的mac地址之后就会以单播方式发送
物理层
- 物理层指的是网线、光纤等连接介质
- 主要是基于电器特性发送高低电压(电信号),高电压对应数字1,低电压对应数字0
sockect套接字
-
什么是socket
- 处于应用层与传输层之间的抽象层,封装与操作系统数据交互的繁琐操作,socket在python中是一个模块
-
socket缓存区
- 创建socket后会分配两个缓冲区,输入缓冲区和输出缓冲区
- send数据后并不是立即向网络中发送数据,而是先写入缓存区,再由tcp协议将数据从缓冲区发送到目标机器,当数据被写入缓冲区send函数就可以成功返回
- 数据写入缓冲区可能立即就会发送到网络,也可能会在缓存区不断堆积,不受程序员控制
- recv同理,也是从输入缓存区中读取数据
- 缓冲区的特性
- 每个套接字都有两个缓冲区,创建套接字时自动生成
- 即使关闭套接字,输出缓存区的数据也会继续发送
- 关闭套接字会丢失输入缓冲区的数据
- 缓冲区的大小一般是8k
- 作用
- 暂时存储数据
- 保证数据的收发稳定和匀速
- 缺点
- socket缓存区是粘包产生的原因之一
基于tcp的socket通信
循环交流
服务端
import socket
tsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 获取tcp套接字
tsock.bind(("127.0.0.1",8808)) # 绑定ip地址和端口号,值是元祖格式
tsock.listen(5) # 设置最大监听数
while 1:
conn,addr = tsock.accept() # 被动接受客户端连接
while 1:
try:
data = conn.recv(1024).decode("utf-8") # 接受客户端消息,可设置最大接受字节数
print(conn,addr)
print(f"来自服务端{addr}的消息:{data}")
conn.send(input(">>>>").encode("utf-8")) # 向客户端发送消息,发送数据量大于缓冲区时数据丢失
# conn.sendall(input(">>>>").encode("utf-8")) # 发送完整的tcp数据,本质是循环调用send,直到数据发送完毕
except Exception:
break
conn.close()
tsock.close() # 关闭套接字
客户端
import socket
tsock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 获取tcp套接字
tsock.connect(("127.0.0.1",8808)) # 初始化tcp服务链接
while 1:
tsock.send(input(">>>>").encode("utf-8")) # 向服务器发送数据
data = tsock.recv(1024).decode("utf-8") # 接受服务器发送的数据,可设置最大接受字节数
print(f"来着服务器的信息:{data}")
tsock.close() # 关闭套接字
粘包
-
粘包现象只存在tcp,udp不会出现粘包
-
出现粘包的情况
- 连续短暂的发送多次(数据量很小),数据会堆积在缓冲区统一发送
- 发送的数据比对方设定的最大接受字节大,接受方第二次拿去数据时,只会拿去前一次剩余的数据
-
解决方案
struct
-
作用
- struct模块可以将一个类型转成固定长度的bytes
-
参数
-
使用方式
import struct ret = struct.pack("i",123) # 转换数字 print(ret) ret = struct.unpack("i",ret) # 反转后获取的是一个元组 print(ret) print(ret[0])
使用struct制作报头
服务端
import socket import subprocess import struct import pickle phone = socket.socket() phone.bind(("127.0.0.1",8808)) phone.listen(3) while 1: conn,addr = phone.accept() while 1: try: data = conn.recv(1024).decode('utf-8') obj = subprocess.Popen(data,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,) # 操作cmd count = obj.stdout.read()+obj.stderr.read() # 指令正取和指令错误返回信息 dic = pickle.dumps({"name":"test","md5":"2314515643524","size":len(count)}) # 制作字典 # print(struct.pack("i", len(count))) conn.send(struct.pack("i",len(dic))) # 发送字典的长度以固定4字节方式 conn.send(dic) # 发送字典 conn.send(count) # 发送数据 except ConnectionResetError: print("连接中断") break conn.close() phone.close()
客户端
import socket import struct import pickle phone = socket.socket() phone.connect(("127.0.0.1",8808)) while 1: count = input(">>>").strip() phone.send(count.encode("utf-8")) # 输入指令 date = b"" heda = struct.unpack("i",phone.recv(4))[0] # 接受四个字节的报头 dic_size = pickle.loads(phone.recv(heda)) # 读取字典的字节并反序列化 while len(date) < dic_size["size"]:# 控制循环读取条件 date += phone.recv(1024) print(f"信息:{date.decode('gbk')}") #默认gbk编码 phone.close()
-
基于udp的socket通信
服务端
import socket
usock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
usock.bind(("127.0.0.1",8080))
while 1:
msg,addr = usock.recvfrom(1024)
print(f"来着客户端的消息:{msg}")
usock.sendto(input(">>>>").encode("utf-8"),addr)
客户端
import socket
usock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while 1:
usock.sendto(input(">>>>").encode("utf-8"),("127.0.0.1",8080))
data,addr = usock.recvfrom(1024)
print(f"来自服务端{addr}的消息:{data.decode('utf-8')}")
进程和线程
进程
-
什么是进程
运行中的程序就是正在运行的程序,进程是资源单位
-
开启一个进程发送了什么
在内存中开启一个进程空间,将主进程的资源复制一份,进程之间空间是隔离的,数据不共享】
-
进程的状态
- 运行一个进程首先会进入就绪状态,拿到cpu后进入运行状态
-
运行:运行中遇到IO进入阻塞状态
- 阻塞:阻塞结束后重新进入就绪状态,等待cpu资源
-
就绪:拿到cpu资源后进入运行状态
-
进程的理论
- 串行
- 逐个完成任务
- 并发
- 一个cpu完成多个任务(cpu快速切换),看起来像同时完成
- 并行
- 多个cpu执行多个任务,真正的同时完成
- 阻塞
- 当cpu在遇到IO就是阻塞
- 非阻塞
- 程序没有IO,就是非阻塞
- 串行
-
创建进程
-
windows系统下必须创建进程必须在mian中
args的参数必须是元祖类型
from multiprocessing import Process def a(name): print(name) if __name__ == '__main__': p = Process(target=a,args=("张三",)) p.start() # 开启进程
from multiprocessing import Process class A(Process): def __init__(self,name): super().__init__() # 继承父类init self.name = name def run(self): # 开启进程自动执行run方法 print(self.name) if __name__ == '__main__': a = A("张三") a.start() # 开启进程
-
-
进程的pid
每个进程都有一个唯一的pid
cmd中可以通过:pid tasklist获取进程pid
python获取进程pid需要导入os模块
from multiprocessing import Process import os def a(): print(os.getpid()) # 获取当前线程pid print(os.getppid()) # 获取父线程的pid if __name__ == '__main__': p = Process(target=a) p.start()
-
join
join是一种阻塞,主进程要等待设置join的子进程执行完毕后再执行
from multiprocessing import Process import time def A(num): print(num) time.sleep(num) if __name__ == '__main__': a = Process(target=A,args=(1,)) ti = time.time() a.start() a.join() print(time.time()-ti)
-
进程对象其他属性
from multiprocessing import Process def a(): pass if __name__ == '__main__': p = Process(target=a) p.start() # 进程名:Process-1 print(p.name) print(Process.is_alive(p))# 获取进程状态 Process.terminate(p) # 杀死进程 print(Process.is_alive(p))
-
守护进程daemon
设置为守护进程的子进程,会在主进程结束后马上结束,设置守护进程需要在开启进程前设置
from multiprocessing import Process import time def a(): time.sleep(1) print("子") if __name__ == '__main__': p = Process(target=a) p.daemon = True p.start() print("主")
-
僵尸进程
在开启进程后,父进程监视子进程运行状态,当子进程运行结束后一段时间内,将子进程回收,父进程和子进程是异步关系,不能在子进程结束后马上捕捉子进程的状态,如果子进程立即释放内存,父进程就无法再检测子进程的状态了,unix提供了一种机制,子进程在结束后会释放大部分内存,但会保留进程号、结束时间、运行状态,供主进程检测和回收
-
什么僵尸进程
子进程在结束后被父进程回收前的状态就是僵尸进程
-
危害
如果父进程由于某些原因一直未对子进程回收,这时子进程就会一直占用内存和进程号
-
解决方式
将父进程杀死,使子进程成为孤儿进程,等待init进行回收
-
-
孤儿进程
父进程已经结束了,但是子进程还在运行,这时子进程就是孤儿进程,孤儿进程由init回收,
进程池
ProcessPoolExecutor:限制开启进程的数量
创建进程池的类:如果指定numprocess为3,则进程池会创建三个进程,然后自始至终 使用这三个进程去执行所有任务,不会开启其他进程
优点:
- 提高操作系统效率
- 减少空间的占用等
from concurrent.futures import ProcessPoolExecutor
import time
import random
def a(num):
print(num)
time.sleep(random.randint(1,4))
if __name__ == '__main__':
p = ProcessPoolExecutor(5) # 默认为cpu核心数
for i in range(20):
p.submit(a,1)
线程
-
什么是线程
进程开启后由线程执行内部代码,线程是执行单位
对比线程和进程
-
io密集型使用多线程
-
计算密集型使用多进程
- 开启进程内存开销非常大,对比开启线程速度很慢,进程间需要借助队列等方式通信
- 开启线程内存开销很小,开启速度很快,线程之间可以共享数据
- 同一进程下的多个线程的pid相同
-
创建线程
from threading import Thread def a(name): print(name) if __name__ == '__main__':# 线程可以不必再main中开启 t = Thread(target=a,args=("张三",)) t.start() # 开启线程 t.setName("线程一") # 设置线程名 print(t.getName()) # 获取线程名 print(t.is_alive()) # 获取线程状态
from threading import Thread class A(Thread): def __init__(self,name): super().__init__() self.name = name def run(self): print(self.name) a = A("张三") a.start()
-
守护线程
等待其他非守护线程和主线程结束后结束,如果守护进程的生命周期小于其他线程则先结束
from threading import Thread import time def a(num): time.sleep(num) if __name__ == '__main__': t = Thread(target=a,args=(1,)) t.daemon = True t.start()
线程池
ThreadPoolExecutor:限制开启线程的数量
from concurrent.futures import ThreadPoolExecutor
import time
import random
def a(num):
print(num)
time.sleep(random.randint(1,4))
if __name__ == '__main__':
t = ThreadPoolExecutor(5) # 默认为cpu核心*5
for i in range(20):
t.submit(a,1)
queue
-
先进先出
import queue q = queue.Queue() q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) print(q.get(timeout=3)) print(q.get(block=True))
-
先进后出,堆栈
import queue q = queue.LifoQueue() q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get()) print(q.get()) print(q.get(timeout=3)) print(q.get(block=True))
-
优先级队列
按照元祖的第一个值的大小输出,先得到最小的
import queue q = queue.PriorityQueue() q.put((1,"A")) q.put((-1,"B")) q.put((0,"C")) print(q.get()) print(q.get()) print(q.get()) print(q.get(timeout=3)) print(q.get(block=True))
event
开启两个线程,一个线程运行到中间的某个阶段,触发另
个线程执行.两个线程增加了耦合性.
from threading import Thread
from threading import Event
import time
event = Event()
def check():
print("检测服务器状态")
time.sleep(3)
print(event.is_set())
event.set()
print(event.is_set())
print("服务器开启")
def conn():
print("等待连接服务器")
event.wait(3) # 三秒后如果还未set任然执行下面代码
print("连接成功")
t1 = Thread(target=check)
t2 = Thread(target=conn)
t1.start()
t2.start()
同步
-
什么是同步
在发出一个功能调用时,在没有得到结果之前,该调用就不会返回。
同步调用
from concurrent.futures import ProcessPoolExecutor
import time
import os
def task(i):
print(f"{os.getpid()}开始")
time.sleep(1.5)
print(f"{os.getpid()}结束")
return i
if __name__ == '__main__':
pr = ProcessPoolExecutor()
for i in range(10):
obj = pr.submit(task,i)
print(obj.result())
pr.shutdown(wait=True)
print("=======")
# shutdown主线程等待子线程结束后再执行
异步
一次性发布多个任务
异步调用
from concurrent.futures import ProcessPoolExecutor
import time
import os
def task(i):
print(f"{os.getpid()}开始")
time.sleep(1.2)
print(f"{os.getpid()}结束")
return i
if __name__ == '__main__':
pr = ProcessPoolExecutor()
li = []
for i in range(10):
li.append(pr.submit(task,i))
pr.shutdown(wait=True)
for i in li:
print(i.result())
异步调用+回调函数
回调函数:按顺序接受每个任务的结果,进行下一步处理
如果进程池+回调: 回调函数由主进程去执行.
如果线程池+回调: 回到函数由空闲的线程去执行.
异步处理的IO类型.
回调处理非IO
import requests
from concurrent.futures import ThreadPoolExecutor
def task(url):
ret = requests.get(url)
if ret.status_code == 200:
return ret.text
def parce(ret):
print(len(ret.result()))
url_list = [
'http://www.baidu.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.JD.com',
'http://www.taobao.com',
'https://www.cnblogs.com/jin-xin/articles/7459977.html',
'https://www.luffycity.com/',
'https://www.cnblogs.com/jin-xin/articles/9811379.html',
'https://www.cnblogs.com/jin-xin/articles/11245654.html',
'https://www.sina.com.cn/',
]
if __name__ == '__main__':
t = ThreadPoolExecutor(5)
for i in url_list:
obj = t.submit(task,i)
obj.add_done_callback(parce)
互斥锁 Lock
-
什么是互斥锁
互斥锁是一种阻塞,可以保证共享数据操作的完整性。保证在任一时刻,只能有一个线程访问该对象,一把锁连续锁两次会导致出现锁死现象
from multiprocessing import Process from threading import Thread from multiprocessing import Lock # 进程锁 # from threading import Lock # 线程锁 import time def a(lock): lock.acquire() # 加锁 print("p1前") time.sleep(1) print("p1后") lock.release() # 解锁 def b(lock): lock.acquire() # 加锁 print("p2前") time.sleep(1) print("p2后") lock.release() # 解锁 if __name__ == '__main__': lock = Lock() # 创建一个锁 p1 = Process(target=a,args=(lock,)) p2 = Process(target=b,args=(lock,)) p1.start() p2.start()
-
死锁
from threading import Thread from threading import Lock import time locka = Lock() lockb = Lock() class Te(Thread): def run(self): self.a() self.b() def a(self): locka.acquire() print(f"{self.name}拿到A锁") lockb.acquire() print(f"{self.name}拿到B锁") lockb.release() print(f"{self.name}释放B锁") locka.release() print(f"{self.name}释放A锁") def b(self): lockb.acquire() print(f"{self.name}拿到B锁") time.sleep(0.1) locka.acquire() print(f"{self.name}拿到A锁") locka.release() print(f"{self.name}释放A锁") lockb.release() print(f"{self.name}释放B锁") if __name__ == '__main__': for i in range(3): t = Te() t.start()
递归锁
递归锁可以解决死锁问题
递归锁有一个计数器,每次上锁都会加一,解锁会减一,直到计数器为零时其他进程才可以进行抢锁
from threading import Thread
from threading import RLock
import time
locka = lockb = RLock()
class Te(Thread):
def run(self):
self.a()
self.b()
def a(self):
locka.acquire()
print(f"{self.name}拿到A锁")
lockb.acquire()
print(f"{self.name}拿到B锁")
lockb.release()
print(f"{self.name}释放B锁")
locka.release()
print(f"{self.name}释放A锁")
def b(self):
lockb.acquire()
print(f"{self.name}拿到B锁")
time.sleep(0.1)
locka.acquire()
print(f"{self.name}拿到A锁")
locka.release()
print(f"{self.name}释放A锁")
lockb.release()
print(f"{self.name}释放B锁")
if __name__ == '__main__':
for i in range(3):
t = Te()
t.start()
信号量
信号量可以规定同一时间抢锁的线程或进程的数量
from threading import Thread,Semaphore
import time
sem = Semaphore(2)
def a(name):
sem.acquire()
print(name)
time.sleep(2)
sem.release()
if __name__ == '__main__':
for i in range(5):
t = Thread(target=a,args=(f"线程{i}",))
t.start()
GIL全局解释器锁
-
什么是GIL锁:
GIL锁是一个解释器级别的锁,保证了同一时刻只能一个线程进入解释器,jpyhon和pypy都没有GIL锁
-
优点:
保证了cpython解释器的数据资源的安全
-
缺点:
单进程的多线程不能利用多核
-
GIL和lock的区别
首先两种锁都是互斥锁,GIL锁保护解释器内部资源数据的安全,上锁和释放锁不需要手动操作,lock锁保护进程或线程中的资源数据安全,是自己定义的,需要自己手动上锁和释放锁
多线程实现socket通信
服务端
from threading import Thread
import socket
def comm(conn,addr):
while 1:
try:
count = conn.recv(1024)
print(f"客户端{addr[1]}:{count.decode('utf-8')}")
conn.send(input(">>>").encode("utf-8"))
except Exception:
break
conn.close()
def accepts():
server = socket.socket()
server.bind(("127.0.0.1",8808))
server.listen(2)
while 1:
conn,addr = server.accept()
t = Thread(target=comm,args=((conn,addr)))
t.start()
if __name__ == '__main__':
accepts()
客户端
import socket
client = socket.socket()
client.connect(("127.0.0.1",8808))
while 1:
try:
client.send(input(">>>").encode("utf-8").strip())
count = client.recv(1024)
print(f"服务端:{count.decode('utf-8')}")
except Exception:
break
client.close()
协程
一个线程并发的处理任务.
方式一:开启多进程并发执行, 操作系统切换+保持状态.
方式二:开启多线程并发执行,操作系统切换+保持状态.
方式三:开启协程并发的执行, 自己的程序 把控着cpu 在3个任务之间来回切换+保持状态.
协程他切换速度非常快,蒙蔽操作系统的眼睛,让操作系统认为cpu一直在运行你这一个线程
优点
开销小
运行速度快.
协程会长期霸占cpu只执行我程序里面的所有任务.
特点
1. 必须在只有一个单线程里实现并发
2. 修改共享数据不需加锁
3. 用户程序里自己保存多个控制流的上下文栈(保持状态)
4. 附加:一个协程遇到IO操作自动切换到其它协程
一般在工作中我们都是进程+线程+协程的方式来实
现并发,以达到最好的并发效果,如果是4核的cpu,
一般起5个进程,每个进程中20个线程(5倍cpu数
量),每个线程可以起500个协程,大规模爬取页面的
时候,等待网络延迟的时间的时候,我们就可以用协程
去实现并发。 并发数量 = 5 * 20 * 500 = 50000个并
发,这是一般一个4cpu的机器最大的并发数。nginx在
负载均衡的时候最大承载量就是5w个
-
greenlet
from greenlet import greenlet import time def a(name): print(name+"A1") g2.switch("这是") time.sleep(2) print(name+"A2") g2.switch() def b(name): print(name+"B1") g1.switch() print(name+"B2") g1 = greenlet(a) g2 = greenlet(b) g1.switch("这是")
import gevent
import time
from gevent import monkey
monkey.patch_all() # 打补丁,将下面所有的任务阻塞都打上标记
def a(name):
print(name + "A1")
time.sleep(2)
print(name + "A2")
def b(name):
print(name + "B1")
time.sleep(1)
print(name + "B2")
g1 = gevent.spawn(a, "这是")
g2 = gevent.spawn(b, "这是")
# g1.join()
# g2.join()
gevent.joinall([g1,g2])
生成者消费者模型
- 三要素
- 生产者:产生数据
- 消费者:接受数据,进行处理
- 容器:队列,缓冲作用,平衡生产力和消费力
from multiprocessing import Process
from multiprocessing import Queue
import time
def a(q):
for i in range(1,10):
count = f"第{i}个数据"
time.sleep(1)
print(f"生产者生产{count}")
q.put(count)
def b(q):
while 1:
try:
count = q.get(timeout=3)
time.sleep(2)
print(f"消费者消费了{count}")
except Exception:
break
if __name__ == '__main__':
q = Queue()
p1 = Process(target=a,args=(q,))
p2 = Process(target=b,args=(q,))
p1.start()
p2.start()