线程队列、事件以及协程

线程的几个队列

都是从queue这个模块中导入

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 实例化出来的对象是先进先出
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 实例化出来的对象是先进的后出
LifoQueue

3、PriorityQueue队列(存储数据时可以设置优先级的队列)

  --1、如果只放一个元素,不用考虑元素能不能比较大小

  --2、只要队列里元素超过两个,那么元素之间必须可以支持比较大小

from queue import PriorityQueue

q = PriorityQueue()

q.put(item={"a":3}) # 这里需要注意的是,如果放入队列是多个元素,那么元素之间必须支持比大小
q.put({"A":2})

print(q.get())  # A
print(q.get())  # a     根据比较完的大小,小的先出


# 我们其实可以自定义一些类,然后给他添加比大小的方法,就可以比较大小了
PriorityQueue

事件Event

1、什么是事件

  事件表示在某个事件发生了某个事情的通知信号,用于线程间的协同工作

  因为不同线程之间是独立运行的状态不可预测,所以一个线程与另一个线程间的数据是不同步的,

  当一个线程需要利用另一个线程的状态来确定自己的下一步操作时,就必须保持线程间数据的同步

2、Event介绍

  event对象包含一个可由线程设置的信号标志,它允许线程等待某些事情发生后才执行

  在初始的情况下,event对象中的信号标志被设置为假,

  如果有一个线程等待一个event对象,而这个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、这样我们可以自己来掌控cpu运行的调度,只要cpu一过来,基本上就能把它的时间片用光,提高程序的效率

  2、当我们并发量比较高的时候,并且线程因为硬件原因已经不能再开启了,这是协程就是一个可以不占

    多少资源,又能实现并发的方法

单线程并发特点

  1、对于计算密集型任务而言,单线程并发并不能提高性能,反而会降低效率

  2、对于IO操作而言,必须具备能够检测IO操作,并自动切换到其他任务,这样才能提高效率

实现单线程并发的方式

1、yield保存状态 + 手动切换(了解)

  并发:多个任务看起来是同时运行,本质上是切换 + 保存状态

  在生成器中,yield就可以保存当前函数的运行状态

  所以我们可以简单通过yield来实现一个线程内的并发

  yield实现的方法是不能检测IO操作的

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封装切换(了解)

  直接使用yield虽然能实现并发了,但是代码的结构太乱(到处都是yield和next),

  greenlet也是需要手动切换,并且不能检测IO操作

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协程

  --1、什么是协程

     gevent 就是协程

     协程也是轻量级线程,也可以称之为微现场

     它是应用程序级别的任务调度方式

     它可以实现任务之间的自动切换,但是无法检测IO,需要和猴子补丁(monkey模块)一起使用

  --2、协程对比线程

    协程是应用程序级别调度,线程是操作系统级别调度

    应用程序级别的调度

      我们在检测到IO操作时,可以立马切换到我的其他任务来执行,如果有足够多的任务执行

      就可以把cpu的时间片充分利用起来

    操作系统级别的调度

      遇到IO,操作系统就会拿走cpu,下一次分给哪一个线程就要看操作系统内部算法,不是我们能决定的

    所以明显可以看出对于一个进程而言,使用协程的效率要高于使用多线的效率

  --3、如何提高效率(使用场景)

    在Cpython中,由于GIL锁的存在,导致多线程并不能并行,丧失了多核优势

    即使开启了多线程也只能并发,这时完全可以用协程来实现并发,提高程序的效率

    优点:不会占用更多无用的资源

    缺点:如果是计算密集型任务,使用协程反而降低效率

  --4、配合猴子补丁的用法

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可以自动切换
posted @ 2019-06-09 20:29  hesujian  阅读(334)  评论(0编辑  收藏  举报