消息队列 IPC机制(进程间通信) 线程理论 开设线程的两种方式 线程实现TCP服务端并发 线程join方法 线程间数据共享 守护线程 GIL全局解释器锁
day36
队列:先进先出(使用频率很高)
堆栈:先进后出(特定场景下用)
消息队列可以实现进程间通信(本地、网络),并且消息队列还起到了保存数据的功能(队列中的数据如果不被取走,会一直在队列中)
消息队列将生产者与消费者解耦合
生产者只需要将数据放入队列中即可 无需考虑是否有人消费
消费者只需要将数据从队列中取出 无需考虑是否有生产者生产
以后我们会直接使用别人封装好的消息队列 实现各种数据传输
代码演示:
from multiprocessing import Queue
q = Queue(5) # 自定义队列的长度
# 朝队列中存放数据
q.put(111)
q.put(222)
q.put(333)
print(q.full()) # False 判断队列是否满了
q.put(444)
q.put(555)
print(q.full()) # True
# q.put(666) # 超出最大长度 原地阻塞等待队列中出现空位
print(q.get())
print(q.get())
print(q.empty()) # False 判断队列是否空了
print(q.get())
print(q.get())
print(q.get())
print(q.empty()) # True
# print(q.get()) # 队列中没有值 继续获取则阻塞等待队列中给值
print(q.get_nowait()) # 队列中如果没有值 直接报错
并发的场景下无法使用下列方法:
full()
empty()
get_nowait()
因为并发的场景下代码是动态的 无法做出正确判断和操作
代码演示:从两个子进程进行交互
from multiprocessing import Process, Queue
def producer(q): # 定义一个生产者函数
print('子进程producer从队列中取值>>>:', q.get())
q.put('子进程producer往队列中添加值')
def consumer(q): # 定义一个消费者函数
print('子进程consumer从队列中取值>>>:', q.get())
if __name__ == '__main__':
q = Queue()
p = Process(target=producer, args=(q, ))
p1 = Process(target=consumer, args=(q,))
p.start()
p1.start()
q.put(123) # 主进程往队列中存放数据123
print('主进程')
'''
运行结果:
主进程
子进程producer从队列中取值>>>: 123
子进程consumer从队列中取值>>>: 子进程producer往队列中添加值
'''
某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。
在实际开发中,由于生产数据和处理数据往往不在同一个线程或者进程,所以缓冲区的作用就体现出来。
如果生产快了,那么就可以先把来不及消费的数据存储到缓冲区,达到缓冲上限就暂停生产,等消费者取出数据再开始生产。
如果生产者的速度时快时慢,缓冲区还能起到稳定的作用,保证消费者获取数据稳定。
这种设计模式最大的好处就是支持忙闲不均。
比如在爬虫领域中
会先通过代码爬取网页数据(爬取网页的代码就可以称之为是生产者)
之后针对网页数据做筛选处理(处理网页的代码就可以称之为消费者)
如果使用进程来演示
除了有至少两个进程之外 还需要一个媒介(消息队列)
以后遇到该模型需要考虑的问题其实就是供需平衡的问题
生产力与消费力要均衡
from multiprocessing import Process, Queue, JoinableQueue
import time
import random
def producer(name, food, q):
for i in range(5):
data = f'{name}生产了{food}{i}'
print(data)
time.sleep(random.randint(1, 3)) # 模拟产生过程
q.put(data)
def consumer(name, q):
while True:
food = q.get()
# if food == None:
# print('完蛋了 没得吃了 要饿死人了')
# break
time.sleep(random.random())
print(f'{name}吃了{food}')
q.task_done() # 每次去完数据必须给队列一个反馈
if __name__ == '__main__':
# q = Queue()
q = JoinableQueue()
p1 = Process(target=producer, args=('厨师1', '韭菜炒蛋', q))
p2 = Process(target=producer, args=('厨师2', '秘制小汉堡', q))
c1 = Process(target=consumer, args=('顾客1', q))
c2 = Process(target=consumer, args=('顾客2', q))
c1.daemon = True
c2.daemon = True
p1.start()
p2.start()
c1.start()
c2.start()
# 生产者生产完所有数据之后 往队列中添加结束的信号
p1.join()
p2.join()
q.put(None) # 结束信号的个数要跟消费者个数一致才可以
q.put(None)
"""队列中其实已经自己加了锁 所以多进程取值也不会冲突 并且取走了就没了"""
q.join() # 等待队列中数据全部被取出(一定要让生产者全部结束才能判断正确)
"""执行完上述的join方法表示消费者也已经消费完数据了"""
print('主线程')
进程:资源单位
负责提供线程执行过程中所需的各项资源
线程:执行单位
真正在进程中干活的人(借助于解释器与CPU交互的)
'''一个进程中至少有一个线程'''
"""
1.进程仅仅是在内存中开辟一块空间(提供线程工作所需的资源)
2.进程内可以创建多个线程
3.进程内多个线程数据共享
"""
为什么要有线程
开设线程的消耗远远小于进程
开进程
1.申请内存空间
2.拷贝代码
开线程
一个进程内可以开设多个线程 无需申请内存空间、拷贝代码
一个进程内的多个线程数据是共享的
"""
开发一个文本编辑器
获取用户输入并实时展示到屏幕上
并实时保存到硬盘中
多种功能应该开设多线程而不是多进程
"""
from threading import Thread
方式1
t = Thread()
t.start()
方式2
class MyThread(Thread)
def run(self):pass
t_obj = MyThread()
t_obj.start()
from threading import Thread
import time
def task(name): # 创个函数
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over')
# 创建线程无需在__main__下面编写 但是为了统一 还是习惯在子代码中写
t = Thread(target=task, args=('jason', ))
t.start() # 创建线程的开销极小 几乎是一瞬间就可以创建
print('主线程')
class MyThread(Thread): # 创个面向对象
def __init__(self, username):
super().__init__()
self.username = username
def run(self):
print(f'{self.username} jason is running')
time.sleep(3)
print(f'{self.username} is over')
t = MyThread('jasonNB')
t.start()
print('主线程')
import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()
def talk(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
while True:
sock, addr = server.accept()
# 每类一个客户端就创建一个线程做数据交互
t = Thread(target=talk, args=(sock,))
t.start()
from threading import Thread
import time
def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over')
t = Thread(target=task, args=('jason', ))
t.start()
t.join() # 主线程代码等待子线程代码运行完毕之后再往下执行
print('主线程')
"""
主线程为什么要等着子线程结束才会结束整个进程
因为主线程结束也就标志着整个进程的结束 要确保子线程运行过程中所需的各项资源
"""
1.验证一个进程下的多个线程是否真的处于一个进程
验证确实如此
2.统计进程下活跃的线程数
active_count() # 获取当前存活的线程 注意主线程也算!!!
3.获取线程的名字方式
1.current_thread().name # 获取线程名
MainThread 主线程的名字
Thread-1、Thread-2 子线程的名字形式
2.self.name # 获取线程名
代码演示:
from threading import Thread, active_count, current_thread
import os
import time
class MyThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
print(self.name)
t1 = MyThread('jason')
t2 = MyThread('kevin')
t1.start()
t2.start()
def task():
time.sleep(1)
print('子线程获取进程号>>>:',os.getpid())
print(current_thread().name)
t = Thread(target=task)
t1 = Thread(target=task)
t.start()
t1.start()
print(active_count())
print(current_thread().name)
print('主线程获取进程号>>>:', os.getpid())
'''
运行结果:
jason
kevin
3
MainThread
主线程获取进程号>>>: 29025
子线程获取进程号>>>: 29025
Thread-3
子线程获取进程号>>>: 29025
Thread-4
'''
from threading import Thread
import time
def task(name):
print(f'{name} is running')
time.sleep(3)
print(f'{name} is over')
t1 = Thread(target=task, args=('jason',))
t2 = Thread(target=task, args=('kevin',))
t1.daemon = True # 守护线程
t1.start()
t2.start()
print('主线程')
"""主线程要等待所有非守护线程结束才可以结束"""
"""纯理论 不影响编程 只不过面试的时候可能会被问到"""
# 官方文档
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.回顾
python解释器的类别有很多
Cpython Jpython Ppython
垃圾回收机制
应用计数、标记清除、分代回收
1.GIL是C python解释器的特点,不是Python语言的的特点
Cpython解释器是最常用的,不刻意指定几乎默认都是它
2.GIL的存在是为了防止CPython解释器垃圾回收机制造成数据不安全
3.GIL本质其实也是一把互斥锁 只不过它用于维护解释器级别的数据安全
针对不同的数据应该加不同的额锁
GIL保护的是代码数据的正常执行
用户自定义的互斥锁保护的是业务数据的安全
4.python同一个进行下的多个线程无法利用多核优势 但是还可以开设多进程
要结合实际情况
如果多个任务都是IO密集型的 那么多线程更有优势(消耗的资源更少)
多道技术:切换+保存状态
如果多个任务都是计算密集型 那么多线程确实没有优势 但是可以用多进程
CPU越多越好
以后用python就可以多进程下面开设多线程从而达到效率最大化
"""
1.所有的解释型语言都无法做到同一个进程下多个线程利用多核优势
2.GIL在实际编程中其实不用考虑