代码之家  ›  专栏  ›  技术社区  ›  nkrkv

python中的快速方法调用调度

  •  3
  • nkrkv  · 技术社区  · 14 年前

    对于我的项目的某些部分,我需要一个进程本地调度系统,它允许我将方法执行延迟几秒钟。我有成千上万的__客户机_这个系统,所以使用 threading.Timer 因为每次延迟都是一个坏主意,因为我将很快达到OS线程限制。我已经 implemented 仅使用一个线程进行定时控制的系统。

    其主要思想是保持排序任务(time+func+args+kwargs)队列并使用单个 线程.定时器 计划/取消执行此队列的头。这个计划行得通,但我对性能不满意。每10秒调度一次虚拟任务的约2000个客户机会导致进程占用40%的CPU时间。查看探查器输出,我发现所有时间都花在 线程.定时器 S构造,它的开始,特别是对新线程的创建。

    我相信有更好的方法。现在我想重写 LightTimer 这样就有一个执行线程可以通过 threading.Event 以及几个计时线程, set() 事件。例如:

    • 我安排一个任务在10秒后调用。任务将添加到队列中。定时线程1启动 time.sleep(10) 之前 event.set()
    • 然后我安排一个任务在11秒后调用。任务将添加到队列中。计时线程没有发生任何变化,它会在唤醒后注意到新任务。
    • 然后我安排一个任务在5秒钟内调用。任务已预先准备到队列。定时线程2启动 time.sleep(5) 因为1已经睡了更长的时间。

    我希望你能理解这个想法。你觉得这样怎么样?有更好的方法吗?也许我可以利用一些Linux系统特性来制定最佳解决方案?

    3 回复  |  直到 14 年前
        1
  •  2
  •   Rakis    14 年前

    您可以使用的另一种实现是 time.time() 方法计算每个排队函数执行的绝对时间。将这一次和要调用的函数放在对象包装器中,该包装器使用执行时间来确定顺序,从而重写比较运算符。然后使用 heapq 模块以保持最小堆。这将为您提供一个有效的数据结构,其中堆的元素0始终是您的下一个事件。

    实现实际调用的一种方法是使用单独的线程来执行回调。堆需要使用互斥保护,您可以使用条件变量来实现调度。在无限循环中,只需查找下一次执行函数(堆的元素0)并使用条件变量的 wait() 方法,将超时设置为下一个执行时间。然后,堆插入方法可以使用条件变量的 notify() 方法,如果新插入的函数早于堆中已有的最早函数,则提前唤醒调度线程。

        2
  •  2
  •   Alex Martelli    14 年前

    你看过吗 sched python标准库中的模块?在一个专用线程上运行调度程序(并且让所有计划的操作都是“将一个绑定的方法及其参数放在一个队列上”,池中的线程从队列中剥离并执行它——就像我在线程的nutshell一章中所写的那样,除了在这种情况下没有调度),应该做您想要做的。

        3
  •  0
  •   MarkR    14 年前

    对于“几千个客户机”,您不太可能达到OS线程限制;但是对于所有这些线程,您可能会使用堆栈消耗大量不必要的内存。

    看看Twisted做了什么,它允许一个进程多路复用许多事件(包括计时器),这种方式已经证明可以很好地处理大量事件。

    您还可以将事件驱动和多进程模型相结合,通过在每台机器上运行多个进程,并在每一个进程中执行事件驱动逻辑,例如一个进程可以处理2000个客户机,您仍然可以运行30个进程(前提是有足够的总体资源),并获得更好的吞吐量,特别是在现代多核硬件上。