zoukankan      html  css  js  c++  java
  • 6月9号

    线程队列、事件以及协程

     

    线程的几个队列

    都是从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可以自动切换
  • 相关阅读:
    ThinkPHP5 API 文档
    【转】移动web页面使用字体的思考
    【原】移动web页面兼容处理的思考
    20140829分享正则大纲
    javascript reverse string
    备忘“与”、“非”、“或”、“异或” 运算
    关于 Apple Metal API 的一些想法
    transform:rotate在手机上显示有锯齿的解决方案
    JS正则表达式验证数字(很全)
    去掉 wap (android/ios)网页等点击后的阴影
  • 原文地址:https://www.cnblogs.com/huikejie/p/10999852.html
Copyright © 2011-2022 走看看