2022.4.20进程补充及线程相关理论、方法概念
2022/4/21 7:20:32
本文主要是介绍2022.4.20进程补充及线程相关理论、方法概念,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
2022.4.20进程补充及线程相关理论、方法概念
- 消息队列
- IPC机制(进程间通信)
- 生产者消费者模型
- 线程理论(重要)
- 开设线程的两种方式
- 线程实现TCP服务端并发
- 线程join方法
- 线程间数据共享
- 线程对象属性及方法
- 守护线程
- GIL全局解释器锁
一、消息队列
ps:由于目前的知识储备还不够直接学习消息队列 所以先学习内置队列,以后我们会直接使用别人封装好的消息队列 实现各种数据传输
from multiprocessing import Queue q = Queue(5) # 定义一个队列并设置长度为5 1.往队列放数据 q.put(111) # 朝队列中存放数据 q.put(222) q.put(333) print(q.full()) # False 判断队列是否满了 q.put(444) q.put(555) print(q.full()) # True 队列已满 此时再往队列放数据就会超出最大长度,原地阻塞等待队列出现空位 2.从队列取数据 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_nowait()) # 队列里没有值直接报错 """ full() empty() get_nowait() 上述方法能否在并发的场景下精准使用??? 不能用!!! 之所以介绍队列是因为它可以支持进程间数据通信 """
二、IPC机制(进程间通信)
1.主进程与子进程数据交互
2.两个子进程数据交互
本质:不同内存空间中的进程数据交互
from multiprocessing import Process, Queue def producer(q): q.put('666') # 往队列放数据 def consumer(q): print(q.get()) # 取队列数据 if __name__ == '__main__': q = Queue() # 设置队列 p1 = Process(target=producer, args=(q, )) p2 = Process(target=consumer, args=(q,)) p1.start() p2.start() # q.put(123) # 主进程往队列中存放数据123 print('主进程') # 结果: 主进程 666
这样不同进程都在操作和使用这个队列,实现进程间通信
三、生产者消费者模型
1、概念
生产者:负责生产/制作数据
消费者:负责消费/处理数据
""" 比如在爬虫领域中: 会先通过代码爬取网页数据(爬取网页的代码就可以称之为是生产者) 之后针对网页数据做筛选处理(处理网页的代码就可以称之为消费者) """ 如果使用进程来演示 除了有至少两个进程之外 还需要一个媒介(消息队列) 以后遇到该模型需要考虑的问题其实就是供需平衡的问题 即生产力与消费力要均衡
2、代码演示
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() time.sleep(random.random()) print(f'{name}吃了{food}') q.task_done() # JoinableQueue队列,每次去完数据必须给队列一个反馈 ''' 问题:生产者p在生产完后就结束了,但是消费者c在取空了q之后,则一直处于死循环中且卡在q.get()这一步,进入阻塞态。 解决方式: 普通方法:无非是让生产者在生产完毕后,往队列中再发一个结束信号,这样消费者在接收到结束信号后就可以break出死循环 JoinableQueue 方法: 生产者生产的每个数据上都做一个标记,消费者每 q.get() 取一个值,都用 q.task_done() 标记一次,q.join()感知队列中的数据全部处理完毕,再最终结束 ''' if __name__ == '__main__': # q = Queue(),这里不使用普通队列 q = JoinableQueue() # 使用JoinableQueue方法 p1 = Process(target=producer, args=('大厨jason', '韭菜炒蛋', q)) p2 = Process(target=producer, args=('老板kevin', '秘制小汉堡', 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.join() # 等待队列中数据全部被取出(一定要让生产者全部结束才能判断正确) """执行完上述的join方法表示消费者也已经消费完数据了,相当于设置一道坎判断队列是否被取完,取完程序结束,守护进程直接结束,不会停留在阻塞态"""
四、线程理论
1、什么是线程
进程:资源单位(在内存空间中申请一块内存)
线程:执行单位(在进程的内存中执行任务,资源从进程空间取)
eg:进程相当于车间,线程相当于车间里面的流水线
一个进程至少有一个线程!
2、为什么要有线程?
答:开设线程的消耗远远小于进程
开进程:申请内存空间--->拷贝代码
开线程:无需申请内存、拷贝代码,进程内多个线程数据共享
总之,开多进程浪费内存空间及资源,线程解决了这个问题
五、开设线程的两种方法
其实开进程和线程的方法几乎是一样的,只不过关键字不同
代码演示:
from threading import Thread import time def task(name): print(f'{name}开始吃饭') time.sleep(3) print(f'{name}吃饱了') # 创建线程无需在__main__下面编写 但是为了统一 还是习惯在子代码中写 t = Thread(target=task, args=('jason',)) t.start() print('主线程') # 结果(创建线程开销很小,几乎一瞬间就可以创建,因此运行很快): jason开始吃饭 主线程 Jason吃饱了 # 用类做线程对象 class MyThread(Thread): def __init__(self, username): self.username = username super().__init__() def run(self): print(f'{self.username}开始吃饭') time.sleep(3) print(f'{self.username}吃饱了') t = MyThread('jason') t.start() print('主线程') # 结果: jason开始吃饭 主线程 Jason吃饱了
六、线程实现TCP服务端的并发
注意区分开设进程和线程的本质区别
import socket from throading import Throad # 这里不用封装函数,因为使用线程做交互 server = socket.socket() server.bind(('127.0.0.1', 8080)) server.listen() def talk(sock): data = sock.recv(1024) # 接收信息 print(data.decode('utf8')) sock.send(data.upper()) # 发送信息 while True: sock, addr = server.accept() # 使用循环,每来一个客户端就创建一个线程做数据交互 t = Throad(target=talk, args=(sock,)) t.start()
因此可以发现,使用线程实现并发更为好一点,因为效率高且节省资源
七、线程join方法
线程的join方法和进程相差无几,都是让子线程运行完毕之后再往下执行
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('主线程') """ 主线程为什么要等着子线程结束才会结束整个进程 因为主线程结束也就标志着整个进程的结束 要确保子线程运行过程中所需的各项资源 """
八、同一进程内多个线程数据共享
from threading import Thread money = 10000000000 def task(): global money money = 1 t = Thread(target=task) t.start() t.join() print(money) 思考:线程更改进程内数据,数据也会被更改 # 1
九、线程对象属性和方法
1、同一进程下多个线程的进程号一致
2、如何统计进程下活跃的线程数
active_aount()
3、获取线程的名字
1.current_throad().name MainThread # 主线程 Thread-1、Thread-2 # 子线程 2.self.name # 类对象获取线程名
十、守护线程
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('主线程') # 结果: jason is running kevin is running 主线程 kevin is over jason is over # 注意,在这里,这个守护进程其实是起不了作用的,因为这里有两个子线程,其中一个是守护线程,但是主线程必须等待所有子线程运行结束才会真正结束,因为可能会有一些需要使用的数据,所以这里守护线程是起不了作用的,除非两个都是守护线程
十一、GIL全局解释器锁
回顾:
python解释器的类别有很多:
Cpython Jpython Ppython
垃圾回收机制:
应用计数、标记清除、分代回收
ps: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.)
解释:
GIL只存在于CPython解释器中,不是python的特征
GIL是一把互斥锁用于阻止同一个进程下的多个线程同时执行
原因是因为CPython解释器中的垃圾回收机制不是线程安全的
强调:
GIL是加在CPython解释器上面的互斥锁,
同一个进程下的多个线程要想执行必须先抢GIL锁,所以同一个进程下多个线程肯定不能同时运行,即:无法利用多核优势
优劣势:
优势:保证数据安全,不会被垃圾回收机制回收
劣势:无法发挥多核优势,但是可以通过开设多进程弥补
实际应用:
多任务为IO密集型:多线程更有优势,消耗资源更少(多道技术:切换+保存状态)
多任务为计算密集型:可以使用多进程,CPU越多越好
这篇关于2022.4.20进程补充及线程相关理论、方法概念的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-01后台管理开发学习:新手入门指南
- 2024-11-01后台管理系统开发学习:新手入门教程
- 2024-11-01后台开发学习:从入门到实践的简单教程
- 2024-11-01后台综合解决方案学习:从入门到初级实战教程
- 2024-11-01接口模块封装学习入门教程
- 2024-11-01请求动作封装学习:新手入门教程
- 2024-11-01登录鉴权入门:新手必读指南
- 2024-11-01动态面包屑入门:轻松掌握导航设计技巧
- 2024-11-01动态权限入门:新手必读指南
- 2024-11-01动态主题处理入门:新手必读指南