线程队列、事件以及协程
2020-12-10 07:02
标签:工作 pre imp 自定义 事件 不用 了解 closed one 都是从queue这个模块中导入 --1、如果只放一个元素,不用考虑元素能不能比较大小 --2、只要队列里元素超过两个,那么元素之间必须可以支持比较大小 事件表示在某个事件发生了某个事情的通知信号,用于线程间的协同工作 因为不同线程之间是独立运行的状态不可预测,所以一个线程与另一个线程间的数据是不同步的, 当一个线程需要利用另一个线程的状态来确定自己的下一步操作时,就必须保持线程间数据的同步 event对象包含一个可由线程设置的信号标志,它允许线程等待某些事情发生后才执行 在初始的情况下,event对象中的信号标志被设置为假, 如果有一个线程等待一个event对象,而这个event对象的标志为假,那么将一直阻塞到标志变为真才接着执行 可用方法 使用案例 协程的目的就是要在单线程中实现并发‘ 1、这样我们可以自己来掌控cpu运行的调度,只要cpu一过来,基本上就能把它的时间片用光,提高程序的效率 2、当我们并发量比较高的时候,并且线程因为硬件原因已经不能再开启了,这是协程就是一个可以不占 多少资源,又能实现并发的方法 1、对于计算密集型任务而言,单线程并发并不能提高性能,反而会降低效率 2、对于IO操作而言,必须具备能够检测IO操作,并自动切换到其他任务,这样才能提高效率 并发:多个任务看起来是同时运行,本质上是切换 + 保存状态 在生成器中,yield就可以保存当前函数的运行状态 所以我们可以简单通过yield来实现一个线程内的并发 yield实现的方法是不能检测IO操作的 直接使用yield虽然能实现并发了,但是代码的结构太乱(到处都是yield和next), greenlet也是需要手动切换,并且不能检测IO操作 --1、什么是协程 gevent 就是协程 协程也是轻量级线程,也可以称之为微现场 它是应用程序级别的任务调度方式 它可以实现任务之间的自动切换,但是无法检测IO,需要和猴子补丁(monkey模块)一起使用 --2、协程对比线程 协程是应用程序级别调度,线程是操作系统级别调度 应用程序级别的调度 我们在检测到IO操作时,可以立马切换到我的其他任务来执行,如果有足够多的任务执行 就可以把cpu的时间片充分利用起来 操作系统级别的调度 遇到IO,操作系统就会拿走cpu,下一次分给哪一个线程就要看操作系统内部算法,不是我们能决定的 所以明显可以看出对于一个进程而言,使用协程的效率要高于使用多线的效率 --3、如何提高效率(使用场景) 在Cpython中,由于GIL锁的存在,导致多线程并不能并行,丧失了多核优势 即使开启了多线程也只能并发,这时完全可以用协程来实现并发,提高程序的效率 优点:不会占用更多无用的资源 缺点:如果是计算密集型任务,使用协程反而降低效率 --4、配合猴子补丁的用法 线程队列、事件以及协程 标签:工作 pre imp 自定义 事件 不用 了解 closed one 原文地址:https://www.cnblogs.com/hesujian/p/10994684.html线程的几个队列
1、Queue队列(先进先出的队列)
from queue import Queue
q = Queue(maxsize=3) # 实例化产生队列对象
# maxsize 设置队列里能容纳的最大的数据个数
q.put("first")
q.put("second")
q.put("third") # 如果队列满了,put会阻塞住,等到空了再放进去
print(q.get()) # first
print(q.get()) # second
print(q.get()) # third 如果队列空了,get会阻塞住,等到有值再取出来
# 从结果中可以看出,queue.Queue 实例化出来的对象是先进先出
2、LifoQueue队列(先进后出的队列,lifo 是 last in first out缩写)
from queue import LifoQueue
q = LifoQueue()
q.put("first")
q.put("second")
q.put("third")
print(q.get()) # third
print(q.get()) # second
print(q.get()) # first
# 从结果可以看出,queue.LifoQueue 实例化出来的对象是先进的后出
3、PriorityQueue队列(存储数据时可以设置优先级的队列)
from queue import PriorityQueue
q = PriorityQueue()
q.put(item={"a":3}) # 这里需要注意的是,如果放入队列是多个元素,那么元素之间必须支持比大小
q.put({"A":2})
print(q.get()) # A
print(q.get()) # a 根据比较完的大小,小的先出
# 我们其实可以自定义一些类,然后给他添加比大小的方法,就可以比较大小了
事件Event
1、什么是事件
2、Event介绍
3、Event使用
from threading import Event,Thread
e = Event()
e.set() # 将event对象的标记设为True,所有被阻塞的线程就进入就绪态,等待操作系统调度
e.is_set() # 返回event对象的标记状态
e.wait(timeout=2) # 如果event对象的标记为False,那就阻塞,True就不阻塞
# 里面可以设置超时时间,如果阻塞超过时间就会接着往下执行
e.clear() # 把event对象的标记重新设为False
# 需求是:两个任务并发执行,但是task2里的over必须要等到task1完毕才能执行
import time
from threading import Event,Thread
e = Event()
def task1():
print("task1 run")
time.sleep(3)
print("task1 over")
e.set() # task1 执行完毕,将事件对象设置为True,让task2里的阻塞变为就绪态,等待操作系统调度
def task2():
print("task2 run")
time.sleep(1)
e.wait() # 让他在这里等待,知道事件对象被设置为True
print("task2 over")
Thread(target=task1).start() # 这里简写,把创建对象和启动线程合并到一起
Thread(target=task2).start()
协程*****
为什么要在单线程内实现并发
单线程并发特点
实现单线程并发的方式
1、yield保存状态 + 手动切换(了解)
def task1():
print("task1 first")
yield
print("task1 second")
yield
def task2():
print("task2 first")
task1().__next__() # 找到task1中第一个yield会返回来接着往下执行
print("task2 second")
task1().__next__() # 需要注意的是,next方法必须要找到生成器中的yield,否则报错
task2() # 最终实现单线程间的并发
2、greenlet封装切换(了解)
import greenlet
def task1():
print("task1 first")
g2.switch()
print("task2 second")
g2.switch()
def task2():
print("task2 first")
g1.switch()
print("task2 second")
g1 = greenlet.greenlet(task1)
g2 = greenlet.greenlet(task2)
g1.switch() # 切换到g1去,执行完毕再接着往下执行,都是需要手动切换
print("main over")
3、gevent协程
import time
from gevent import monkey
# 先导入monkey的类
monkey.patch_all() # 可以修改一些阻塞代码变成非阻塞代码(具体可以修改哪些可以点进去看)
import gevent # 导入gevent
def task1():
print("task1 first")
time.sleep(1)
print("task1 second")
def task2():
print("task2 first")
print("task2 second")
g1 = gevent.spawn(task1) # 创建协程
g2 = gevent.spawn(task2)
gevent.joinall([g1,g2]) # 注意的是主线程需要等待这些任务完成,不然主线程已结束,任务都不会执行
print("main over") # 运行结果也可以看出这些任务之间都是并发执行的,并且遇到IO可以自动切换