美文网首页
Python中轮询触发更替事件驱动的简单方法

Python中轮询触发更替事件驱动的简单方法

作者: slords | 来源:发表于2017-07-28 16:54 被阅读0次

在处理事件队列的过程中不少情况是采用轮询的方式进行的。

例如如下例子所示。在Example中,主线程和子线程通过队列的形式进行通信,为模拟业务,主线程将随时获取到的待处理任务放入对应的任务队列(将一随机数放入随机的队列中,之后等待随机的时间),子线程发现有队列中有待处理的事件就将其取出进行处理(取出队列中的数字进行打印)。

import time
from collections import deque
from random import Random
import threading

class Example():

    def __init__(self):
        self.r = Random(time.time())

    def worker_run(self, name, q):
        while True:
            if q:
                print("%s pop number: %s, %s numbers left" % (name, q.pop(), len(q)))
            time.sleep(0.001)

    def manager_run(self,worker_num):
        queue_list = [deque() for i in range(worker_num)]
        
        worker_list = [threading.Thread(target=self.worker_run, 
            args=["Thread-%s" % i, queue_list[i]]) for i in range(worker_num)]
        for worker in worker_list:
            worker.start()
        while True:
            i = 0
            while i< self.r.randint(0,20):
                queue_list[self.r.randint(0,worker_num - 1)].appendleft(
                        self.r.randint(1,100)
                    )
                i += 1
            time.sleep(self.r.random()/100)

if __name__ == '__main__':
    Example().manager_run(3)

进行功能分离简单,目的性明确。但是存在效率性的问题,如果想提高触发效率,那么需要将worker_run中的每轮sleep时间设置的非常小,甚至是0。这么就造成了另一个问题,CPU的损耗问题。无意义的while死循环无疑会造成CPU的无意义损耗。这个对机器性能消耗非常巨大的。所以,需要对机制进行修改,由轮询转为事件驱动。这样的更改无疑会对代码整体结构有较大的调整。但是是否有简单的方式可以快速达到目的,且更代码改较量小。

不难想到,只要在事件队列为空时暂停工作线程,队列放入值时重启工作线程即可。原本,直接对线程进行中断和继续是最好的解决方案,但是在python中threading.Thread并不支持。在官方文档如是说:

The design of this module is loosely based on Java’s threading model. However, where Java makes locks and condition variables basic behavior of every object, they are separate objects in Python. Python’s Thread
class supports a subset of the behavior of Java’s Thread class; currently, there are no priorities, no thread groups, and threads cannot be destroyed, stopped, suspended, resumed, or interrupted. The static methods of Java’s Thread class, when implemented, are mapped to module-level functions.

这意味着,没有一种方法直接对子线程进行暂停、重启等。所以换了一个思路,使用threading.Event实现信号量作为暂停重启的替代方案。当需要暂停时,使用threading.Event.wait()等待信号。当需要继续时,使用threading.Event.set()发出信号。这样就解决了线程暂停、继续的问题。

另一个问题是如何在队列发生改变时触发信号量的更改,不同的变量可以通过不同的方式实现,事实上每一种变量都用不同的解决方案。

  • 对于内部实例化的示例可以通过继承重新构建同名class的方式,重写修改内容的方法实现触发信号量。
  • 外部变量则可以通过methodType的方式绑定重写对于的方法。
  • 实例的基础类型属性则可以通过@property的方式在setter中加入信号触发。
  • ...

所以可以对上述示例进行如下修改。

import time
from collections import deque as dq
from random import Random
import threading

#重写用于事件队列的deque,使得对deque的变动会触发信号量
class deque(dq):
    #存储信号的集合
    sign = set()
    #重写append、appendleft、extend、extendleft方法
    def append(self, *args, **kwargs):
        dq.append(self, *args, **kwargs)
        self.sign_set()

    def appendleft(self, *args, **kwargs):
        dq.appendleft(self, *args, **kwargs)
        self.sign_set()
        
    def extend(self, *args, **kwargs):
        dq.extend(self, *args, **kwargs)
    
    def extendleft(self, *args, **kwargs):
        dq.extendleft(self, *args, **kwargs)

    #触发信号
    def sign_set(self):
        for s in self.sign:
            s.set()

    #添加一个信号
    def add_sign(self,s):
        self.sign.add(s)

class Example():

    def __init__(self):
        self.r = Random(time.time())
    
    #添加一个信号量绑定给deque,当发现deque中没有值之后等待信号传入
    def worker_run(self, name, q):
        sign = threading.Event()
        q.add_sign(sign)
        while True:
            if q:
                print("%s pop number: %s, %s numbers left" % (name, q.pop(), len(q)))
            else:
                sign.wait()
                sign.clear()

    def manager_run(self,worker_num):
        queue_list = [deque() for i in range(worker_num)]
        
        worker_list = [threading.Thread(target=self.worker_run, 
            args=["Thread-%s" % i, queue_list[i]]) 
            for i in range(worker_num)]
        for worker in worker_list:
            worker.start()
        while True:
            i = 0
            while i< self.r.randint(0,20):
                queue_list[self.r.randint(0,worker_num - 1)].appendleft(
                        self.r.randint(1,100)
                    )
                i += 1
            time.sleep(self.r.random())

if __name__ == '__main__':
    Example().manager_run(3)

通过这种方式最小限度的更改代码,几乎无代码结构变动。就可以将轮询触发变为事件驱动,在不影响原有业务的前提下降低系统的无意义负载。

相关文章

  • Python中轮询触发更替事件驱动的简单方法

    在处理事件队列的过程中不少情况是采用轮询的方式进行的。 例如如下例子所示。在Example中,主线程和子线程通过队...

  • 事件循环 & 继承 杂记

    事件循环 事件: 键盘事件, 其他东西触发的, 统称为事件 轮询: 操作系统通过轮询的方式, 每个一段时间就询问事...

  • Netty之线程模型

    1、事件驱动模型 通常,我们设计一个事件处理模型的程序有两种思路: 轮询方式,线程不断轮询访问相关事件发生源有没有...

  • 事件

    事件触发方法:onclick="单击触发事件";ondblclick="双击触发事件";onmousedown="...

  • 事件流

    Javascript程序使用的是事件驱动的程序设计模型,当文档中某些元素触发了特定事件时,事件从window开始,...

  • 游戏AI-AI角色对环境信息的感知

    轮询方式 如果想知道周围世界发生了什么,最简单的方法是查询,AI对感兴趣的事件进行查询,基于轮询的感知系统更容易维...

  • Node模块之事件(events)详解

    Node中的事件模型就是我们常见的订阅发布模式,Nodejs核心API都采用异步事件驱动,所有可能触发事件的对象都...

  • Python的Twisted事件驱动的网络引擎框架

    Python的Twisted事件驱动的网络引擎框架 概述 Twisted是用Python实现的基于事件驱动的网络引...

  • Taro入门(四)——事件处理与运行环境

    事件处理: 需要进行事件触发,在当前类中定义方法,由事件触发后进行调用,有一个默认参数,就是事件源元素 在事件中我...

  • 《PyQT5软件开发 - 基础篇》第5章 PyQt5事件和信号

    5.1事件 Event 所有的GUI程序都是事件驱动的。事件主要由用户触发,但也可能有其他触发方式:例如网络连接、...

网友评论

      本文标题:Python中轮询触发更替事件驱动的简单方法

      本文链接:https://www.haomeiwen.com/subject/pqadkxtx.html