zoukankan      html  css  js  c++  java
  • day38 各种队列、Event事件、协程、猴子补丁

    1、各种队列

    我们已经学习了队列这种存取值的方法,我们以前使用的队列是可以进行进程间通信的(IPC),但是今天学习的这两种队列是不能进行进程间通信的,只能进行线程间的通信

    这两种队列分别是先进后出式队列Lifoqueue、优先级队列PriorityQueue

    from queue import LifoQueue
    ​
    lf = LifoQueue()
    lf.put(111)
    lf.put(222)
    lf.put(333)
    ​
    print(lf.get())
    print(lf.get())
    print(lf.get())
    ​
    # 333
    # 222
    # 111
    # 取值规则为先进后出
    from queue import PriorityQueue
    ​
    pq = PriorityQueue()
    pq.put((1,2))
    pq.put((1,3))
    pq.put((1,4))
    ​
    print(pq.get())
    print(pq.get())
    print(pq.get())
    ​
    # (1,2)
    # (1,3)
    # (1,4)
    # 规则是按照优先级进行对比,会先对比容器中的第一个元素,如果第一个一样则对比第二个元素,直至排出优先级
    # 最高的那个元素

    扩展知识: 大小比较的本质

    关于为什么有的对象可以进行大小的比较,有的对象不能进行大小的比较

    https://blog.csdn.net/zhangshuaijun123/article/details/82149056

    2、Event事件

    import time
    from threading import Event, Thread
    ​
    event = Event()
    ​
    flag = False
    ​
    def sever():
        print("正在开启服务器...")
        time.sleep(3)
        print("服务器开启成功")
        # global flag
        # flag = True
        event.set()
    ​
    ​
    def client():
        # print("正在连接...")
        # if flag:
        event.wait()
        print("连接成功")
        # else:
        #     print("连接失败")
    # 假如我们想要在 服务器开启成功后在进行连接,用这种办法肯定不行,那么就需要在开启成功后添加一个标志,
    # 只有在标志修改后直接进行后续步骤
    # event就可以创建这种标志
    ​
    t = Thread(target=sever)
    t1 = Thread(target=client)
    t.start()
    t1.start()
    ​
    print("over")

    Event事件的作用是进行进程间通讯,将进程间进行状态的同步,我们可以在某个点设置event.set(),那么当其前方代码执行完毕后,就会发出一个信号,使得event.wait()方法不再阻塞,进而执行wait()之后的代码

     

    3、协程

    在我们使用多线程实现并发的过程中,如果并发量比较大,那么我们应该如何处理?

    此时你可能会说开启多线程,但是如果并发量达到了百万或者千万级别,那应该如何呢?

    此时使用多线程不可能实现,但是如果使用多进程+多线程可以进行处理,但是如果更多呢?

    因为进程以及线程的开启数量是有限的,如果开启过多可能会造成系统不稳定,那么此时可以使用我们今天使用的内容 —— 协程

    学习协程之前我们首先需要复习一下什么是并发,并发指的是在同一个时间段,看起来有几个进程在同时运行,但是实际上同一时间只有一个线程在执行,我们以前学了多线程实现并发,那么一个线程可不可以实现并发呢?

    首先来看一下我们已经学过的生成器

    import time
    ​
    def func():
        a = 1
        for i in range(100000000):
            a += 1
            # yield
    ​
    ​
    def func2():
        # s = func()
        a = 1
        for i in range(100000000):
            a += 1
            # next(s)
    ​
    ​
    st = time.time()
    func()
    func2()
    print(time.time() - st)
    # 在使用串行执行时,耗时为15秒,在使用生成器时,耗时为30秒

    我们通过上述生成器的例子可以看出,在同一个进程中可以同时并发的执行两个函数,这已经实现了并发,但是,在上述例子中,并发虽然实现了,但是程序的执行效率却一点都没有提高,反而执行效率都降低了,

    上述的例子,我们进行的都是数据的计算,那么如果我们将数据的计算换成IO操作呢?

    可能你会想,实现IO操作也不行啊,因为即使达到了并行的效果,但是我们的程序在执行到IO操作时,还是会等待,只有在IO操作执行完毕才会进行后续的取值工作,这时候还是在进行等待,而且我们在进行多个函数之间的切换时,要写好多next取值的代码,整体逻辑也不清晰,那么有没有一种方法对这些进行了改变呢?

    我们烦恼的问题,前人也遇到了,而且他们也进行了处理,有一个包叫做gevent,他可以帮我们处理这些烦人的问题

    from gevent import monkey
    monkey.patch_all()
    ​
    import time
    import gevent
    ​
    ​
    ​
    def func():
        print("func1 run")
        time.sleep(3)
        print("func1 over")
    ​
    def func2():
        print("func2 run")
        time.sleep(5)
        print("func2 over")
    ​
    ​
    g1 = gevent.spawn(func)
    g2 = gevent.spawn(func2)
    ​
    gevent.joinall([g1,g2])

    使用这个包可以将我们烦恼的问题全部解决,首先是进行等待的问题,在使用这个包进行单线程并发时,当一个函数遇到了IO操作,这个方法不会进行等待,而是直接切换到其他方法继续执行,因为对于操作系统来说最小的执行单位是线程,所以一个线程如果不进行IO操作,那么只有在这个时间片用完之后在进行进行切换,直接提高了我们使用CPU的时间,当我们使用gevent进行单线程并发时,如果遇到IO就切换到同线程下的其它函数进行执行,那么操作系统就不会将CPU切走,进而提高我们的程序对于CPU的使用

    那么这个模块是如何实现这个不等待直接切换执行呢?

    因为在这些IO操作中大部分都封装了不等待的方法,当时这些方法默认时开启的,如果设置为False,那么这些模块在进行执行时如果遇到了这些要进行IO操作,如果没有值就会报错,那么再将这些错误进行捕捉,再在except中进行切换就可以了

    与此同时,新的疑问出现了,他是怎么将系统中的错误进行捕捉的呢?在我们的代码中并没有对这些错误进行处理,而且我们只是将这些函数作为参数传递给了这个模块进行执行

    当我们将其中的monkey.patch_all()代码进行删除后,这些代码又会按照原来的执行方式进行执行了,这是因为这个monkey方法对这些产生阻塞的方法进行了偷梁换柱,已经将这些阻塞的方法换成了自己的代码,所以要将这段代码放在最上方,先导入其中的方法

    4、猴子补丁

    猴子补丁的原理是重新写带有阻塞的类中的方法,我们在使用猴子补丁时实际上已经把这些方法进行了覆盖

    import json
    ​
    import gevent
    from gevent import monkey
    monkey.patch_all()
    ​
    def func(args):
        print("这是一个假的方法")
    ​
    def func1(args):
        print("这是一个假的方法")
    ​
    def my_patch():
        json.load = func
        json.loads = func1
    ​
    ​
    my_patch()
    ​
    json.loads("aaa")
    json.load("aaa")
    ​
    # 这是一个假的方法
    # 这是一个假的方法

    使用猴子补丁可以利用名称空间的查找规格将系统的方法屏蔽,将我们定义的方法覆盖覆盖原方法,然后再进行调用

     

  • 相关阅读:
    Flask基本介绍
    【Maven】使用Maven构建多模块项目
    spring data jpa 详解
    request.getParameterNames()和request.getParameterValues()
    JAVA字符串格式化-String.format()的使用
    Java中的String,StringBuilder,StringBuffer三者的区别
    Java总结篇系列:Java泛型
    <c:forEach>详解
    Spring MVC 相关资料整理
    关于${pageContext.request.contextPath}的理解 (转载)
  • 原文地址:https://www.cnblogs.com/lice-blog/p/10986926.html
Copyright © 2011-2022 走看看