消息队列和线程
-
消息队列
-
IPC机制(进程间通信)
-
生产者消费模型
-
线程理论(重要)
-
开设线程的两种方式
-
线程实现TCP服务端并发
-
线程join方法
-
线程间数据共享
-
守护进程
-
GIL全局解释器锁
-
消息队列
队列: 先进先出(使用频率高)
堆栈: 先进后出(特定常见下用)
# Queue队列
from multiprocessing import Queue
q = Queue(6) # 自定义队列的长度
q.put(11) # put可以朝队列中存放数据
q.put(22)
q.put(33)
print(q.full()) # False 判断队列是不是已经满了
q.put(44)
q.put(55)
q.put(66) # True
print(q.full())
print(q.get())
print(q.get())
print(q.empty()) # False 判断队列是不是被取完了
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.empty()) # True
print(q.get_nowait()) # 直接报错, 查看队列中是否还有值, 没有就报错
"""
full()、 empty()、 get_nowait() 这些都不能在并发场景下精准使用
"""
-
IPC机制(进程间通信)
"""
1.主进程和子进程进行数据交互
2.两个子进程进行数据交互
本质就是不同内存空间中的进程数据交互
"""
from multiprocessing import Process, Queue
def producer(q):
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()
print('主进程')
-
生产者消费者模型
# 生产者含义
负责生产>>>: 制作数据
# 消费者含义
负责消费>>>: 处理数据
eg:
在爬虫领域中,会先通过代码爬取网页数据(爬取网页的代码就可以称之为生产者)
后面针对网页数据做筛选处理(处理网页的代码就可以称之为消费者)
如果用进程演示,除了至少有两个进程之外,还需要一个媒介(消息队列)
还要考虑供需平衡——生产力和消费力的均衡问题
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=('熊大', '烤鱼', q))
p2 = Process(target=producer, args=('熊二', '蜂蜜', q))
c1 = Process(target=consumer, args=('光头强', q))
c2 = Process(target=consumer, args=('吉吉国王', 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() # 等待队列中的值被取完,一定要让生产者全部结束才能评判正确
-
线程理论
# 线程含义
进程:资源单位
线程:执行单位
进程相当于车间(单个空间),线程相当于车间里面的流水线员工(真正干活的)
""" 一个进程中必须要有一个线程 """
"""
进程仅仅是在内存空间中找到一块空间用来提供线程工作所需的资源
线程被cpu执行, 线程需要的资源跟所在进程的索要
"""
# 为啥要有线程呢
开设线程的消耗远远小于进程
开进程步骤:
1、申请内存空间
2、拷贝代码
开线程步骤:
一个进程内可以开设多个线程, 无需申请内存空间、拷贝代码
一个进程内的多个线程是共享的
# 开发一个文本编辑器
1、获取用户输入并实时展示到屏幕上
2、并实时保存到硬盘里面
多种功能应该开设多线程,而不是多进程
-
开设线程的两种方式
1、
# from threading import Thread
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=('owen', ))
t.start()
print('主线程')
from threading import Thread
import time
class MyThread(Thread):
def __init__(self, username):
super().__init__()
self.username = username
def run(self):
print(f'{self.username} is running')
time.sleep(3)
print(f'{self.username} is over')
t = MyThread('owen')
t.start()
print('主线程')
-
线程实现TCP服务端的并发
# 服务端
import socket
from threading import Thread
server = socket.socket()
server.bind(('127.0.0.1', 8888))
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()
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8888))
while True:
client.send(b'mamahaha')
data = client.recv(1024)
print(data.decode('utf8'))
-
线程join方法
# join方法就是主线程代码等待子线程代码运行结束之后再往下执行
from threading import Thread
import time
def task(name):
print(f'{name} is runing')
time.sleep(3)
print(f'{name} is over')
t = Thread(target=task, args=('owen', ))
t.start()
t.join()
print('主线程')
"""
主线程为啥要等到子线程结束才结束整个过程
因为要确保子线程运行过程中需要的各项资源正常供给,主线程才能放心结束
"""
-
同一个进程内的多个线程数据共享
from threading import Thread
money = 9999999
def task():
global money
money = 9
t = Thread(target=task)
t.start()
t.join()
print(money)
-
线程对象属性和方法
1、验证一个进程下的多个线程是否真的处于一个进程
from threading import Thread
import os
import time
def task():
print('子线程进程号', os.getpid())
t = Thread(target=task)
t.start()
time.sleep(3)
print('主线程进程号', os.getpid())
# 子线程进程号 20424
# 主线程进程号 20424
进程号相同, 所以是一个进程
2、统计进程下活跃的线程
import threading
print(threading.active_count())
3、获取线程的名字
from threading import Thread
class MyThread(Thread):
def run(self):
print(self.name)
t1 = MyThread()
t2 = MyThread()
t1.start()
t2.start()
# Thread-1 子线程
# Thread-2 子线程
2.self.name
-
守护线程
from threading import Thread
import time
def task(name):
print(f'{name} is runing')
time.sleep(3)
print(f'{name} is over')
t1 = Thread(target=task, args=('owen', ))
t2 = Thread(target=task, args=('owen', ))
t1.daemon = True
t1.start()
t2.start()
print('主线程')
##
owen is runing
owen is runing
主线程
owen is over
owen is over
-
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.)
"""
python解释器的类别有很多
Cpython Jpython Ppython
垃圾回收机制
应用计数、标记清除、分代回收
GIL只存在于CPython解释器中,不是python的特征
GIL是一把互斥锁用于阻止同一个进程下的多个线程同时执行
原因是因为CPython解释器中的垃圾回收机制不是线程安全的
反向验证GIL的存在 如果不存在会产生垃圾回收机制与正常线程之间数据错乱
GIL是加在CPython解释器上面的互斥锁
同一个进程下的多个线程要想执行必须先抢GIL锁 所以同一个进程下多个线程肯定不能同时运行 即无法利用多核优势
强调:同一个进程下的多个线程不能同时执行即不能利用多核优势
python是垃圾 速度太慢 有多核都不能用
反怼:虽然用一个进程下的多个线程不能利用多核优势 但是还可以开设多进程!!!
再次强调:python的多线程就是垃圾!!!
反怼:要结合实际情况
如果多个任务都是IO密集型的 那么多线程更有优势(消耗的资源更少)
多道技术:切换+保存状态
如果多个任务都是计算密集型 那么多线程确实没有优势 但是可以用多进程
CPU越多越好
以后用python就可以多进程下面开设多线程从而达到效率最大化
"""
1.所有的解释型语言都无法做到同一个进程下多个线程利用多核优势
2.GIL在实际编程中其实不用考虑
分类:
python基础
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下