簡體   English   中英

消費者/生產者“及時”排隊

[英]Consumer/Producer “timely” queue

我已經實現了消費者/生產者優先級隊列,其中優先級實際上是一個時間戳,代表何時應交付該項目。 它工作得很好,但是我想知道是否有人有更好的想法來實現這一點或對當前實現發表評論。

該代碼在Python中。 創建了一個線程來按時喚醒等待中的使用者。 我知道這是在庫中創建線程的反模式,但是我無法設計其他方法。

這是代碼:

import collections
import heapq
import threading
import time

class TimelyQueue(threading.Thread):
    """
    Implements a similar but stripped down interface of Queue which
    delivers items on time only.
    """

    class Locker:
        def __init__(self, lock):
            self.l = lock
        def __enter__(self):
            self.l.acquire()
            return self.l
        def __exit__(self, type, value, traceback):
            self.l.release()

    # Optimization to avoid wasting CPU cycles when something
    # is about to happen in less than 5 ms.
    _RESOLUTION = 0.005

    def __init__(self):
        threading.Thread.__init__(self)
        self.daemon = True
        self.queue = []
        self.triggered = collections.deque()
        self.putcond = threading.Condition()
        self.getcond = threading.Condition()
        # Optimization to avoid waking the thread uselessly.
        self.putwaketime = 0

    def put(self, when, item):
        with self.Locker(self.putcond):
            heapq.heappush(self.queue, (when, item))
            if when < self.putwaketime or self.putwaketime == 0:
                self.putcond.notify()

    def get(self, timeout=None):
        with self.Locker(self.getcond):
            if len(self.triggered) > 0:
                when, item = self.triggered.popleft()
                return item
                self.getcond.wait(timeout)
            try:
                when, item = self.triggered.popleft()
            except IndexError:
                return None
            return item

    def qsize(self):
        with self.Locker(self.putcond):
            return len(self.queue)

    def run(self):
        with self.Locker(self.putcond):
            maxwait = None
            while True:
                curtime = time.time()
                try:
                    when, item = self.queue[0]
                    maxwait = when - curtime
                    self.putwaketime = when
                except IndexError:
                    maxwait = None
                    self.putwaketime = 0
                self.putcond.wait(maxwait)

                curtime = time.time()
                while True:
                    # Don't dequeue now, we are not sure to use it yet.
                    try:
                        when, item = self.queue[0]
                    except IndexError:
                        break
                    if when > curtime + self._RESOLUTION:
                        break

                    self.triggered.append(heapq.heappop(self.queue))
                if len(self.triggered) > 0:
                    with self.Locker(self.getcond):
                        self.getcond.notify()


if __name__ == "__main__":
    q = TimelyQueue()
    q.start()

    N = 50000
    t0 = time.time()
    for i in range(N):
        q.put(time.time() + 2, i)
    dt = time.time() - t0
    print "put done in %.3fs (%.2f put/sec)" % (dt, N / dt)
    t0 = time.time()
    i = 0
    while i < N:
        a = q.get(3)
        if i == 0:
            dt = time.time() - t0
            print "start get after %.3fs" % dt
            t0 = time.time()
        i += 1
    dt = time.time() - t0
    print "get done in %.3fs (%.2f get/sec)" % (dt, N / dt)

您真正需要的唯一的背景線程是一個計時器,當它用完時,它會踢服務員,對嗎?

首先,可以使用threading.Timer而不是顯式的后台線程來實現。 但是,盡管這可能更簡單,但它並不能真正解決您在用戶背后創建線程的問題,無論他們是否想要。 同樣,使用threading.Timer ,實際上每次重新啟動計時器時都會分離出一個新線程,這可能是性能問題。 (一次只能有一個線程,但是啟動和停止線程並不是免費的。)

如果您查看PyPI模塊,ActiveState配方和各種框架,那么有許多實現可讓您在單個后台線程上運行多個計時器。 那將解決您的問題。

但這仍然不是一個完美的解決方案。 例如,假設我的應用需要20個TimelyQueue對象-或一個TimelyQueue對象,以及19個其他都需要計時器的東西。 我仍然會得到20個線程。 或者說我正在構建一個套接字服務器或GUI應用程序( TimelyQueue的兩個最明顯的用例;我可以在事件循環的頂部實現一個計時器(或者,很可能僅使用附帶的計時器)框架),那么為什么我根本不需要線程?

解決的辦法是提供一個掛鈎來提供任何計時器工廠:

def __init__(self, timerfactory = threading.Timer):
    self.timerfactory = timerfactory
    ...

現在,當您需要調整計時器時:

if when < self.waketime:
    self.timer.cancel()
    self.timer = self.timerfactory(when - now(), self.timercallback)
    self.waketime = when

對於快速和骯臟的用例,這是開箱即用的好方法。 但是,例如,如果我使用twisted ,那么我可以使用TimelyQueue(twisted.reactor.callLater) ,現在隊列的計時器將通過twisted事件循環。 或者,如果我有一個多計時器單線程實現,我在其他地方使用了TimelyQueue(multiTimer.add) ,現在隊列的計時器與我所有其他計時器都在同一線程上。

如果願意,您可以提供比threading.Timer更好的默認值,但實際上,我認為大多數需要比threading.Timer更好的東西的人都可以為自己的特定應用程序提供比您提供的任何東西更好的東西。

當然,並非每個計時器實現都具有與threading.Timer相同的API-盡管您會驚訝地發現其中有很多功能。 但是編寫適配器並不難,如果您有要與TimelyQueue一起使用的計時器,但其接口錯誤。 例如,如果我正在構建PyQt4 / PySide應用程序,則QTimer沒有cancel方法,並且需要ms而不是秒,因此我必須執行以下操作:

class AdaptedQTimer(object):
    def __init__(self, timeout, callback):
        self.timer = QTimer.singleShot(timeout * 1000, callback)
    def cancel(self):
        self.timer.stop()

q = TimelyQueue(AdaptedQTimer)

或者,如果我想更直接地將隊列集成到QObject ,則可以包裝QObject.startTimer()並讓我的timerEvent(self)方法調用回調。

一旦考慮適配器,最后一個想法。 我認為這不值得,但可能值得考慮。 如果你的計時器采取了時間戳,而不是timedelta,並具有adjust方法,而不是/而不是cancel ,並舉行了自己的waketime ,你TimelyQueue實現可以是簡單的,並且可能更有效。 put ,你有這樣的事情:

if self.timer is None:
    self.timer = self.timerfactory(when)
elif when < self.timer.waketime:
    self.timer.adjust(when)

當然,大多數計時器都不提供此接口。 但是,如果有人擁有一個,或者願意制作一個,那么他們可以獲得好處。 對於其他所有人,您可以提供一個簡單的適配器,將一個threading.Timer樣式的計時器轉換為所需的類型,例如:

def timerFactoryAdapter(threadingStyleTimerFactory):
    class TimerFactory(object):
        def __init__(self, timestamp, callback):
            self.timer = threadingStyleTimerFactory(timestamp - now(), callback)
            self.callback = callback
        def cancel(self):
            return self.timer.cancel()
        def adjust(self, timestamp):
            self.timer.cancel()
            self.timer = threadingStyleTimerFactory(timestamp - now(), self.callback)

作為記錄,我已經使用計時器工廠實現了您的建議。 我使用上述版本運行了一個小型基准,並使用threading.Timer類運行了新版本:

  1. 第一次實施

    • 使用默認分辨率(5毫秒,即5毫秒窗口中的所有內容一起觸發),它可以實現約88k put() / sec和69k get() / sec。

    • 將分辨率設置為0 ms(無優化)時,它可以達到約88k put() / sec和55k get() / sec。

  2. 第二實施

    • 使用默認分辨率(5毫秒),它可以達到約88k put() / sec和65k get() / sec。

    • 將分辨率設置為0 ms,它可以達到約88k put() / sec和62k get() / sec。

我承認我很驚訝沒有分辨率優化的第二種實現會更快。 現在進行調查為時已晚。

import collections
import heapq
import threading
import time

class TimelyQueue:
    """
    Implements a similar but stripped down interface of Queue which
    delivers items on time only.
    """

    def __init__(self, resolution=5, timerfactory=threading.Timer):
        """
        `resolution' is an optimization to avoid wasting CPU cycles when
        something is about to happen in less than X ms.
        """
        self.resolution = float(resolution) / 1000
        self.timerfactory = timerfactory
        self.queue = []
        self.triggered = collections.deque()
        self.putcond = threading.Condition()
        self.getcond = threading.Condition()
        # Optimization to avoid waking the thread uselessly.
        self.putwaketime = 0
        self.timer = None
        self.terminating = False

    def __arm(self):
        """
        Arm the next timer; putcond must be acquired!
        """
        curtime = time.time()
        when, item = self.queue[0]
        interval = when - curtime
        self.putwaketime = when
        self.timer = self.timerfactory(interval, self.__fire)
        self.timer.start()

    def __fire(self):
        with self.putcond:
            curtime = time.time()
            debug = 0
            while True:
                # Don't dequeue now, we are not sure to use it yet.
                try:
                    when, item = self.queue[0]
                except IndexError:
                    break
                if when > curtime + self.resolution:
                    break

                debug += 1
                self.triggered.append(heapq.heappop(self.queue))
            if len(self.triggered) > 0:
                with self.getcond:
                    self.getcond.notify(len(self.triggered))
            if self.terminating:
                return
            if len(self.queue) > 0:
                self.__arm()

    def put(self, when, item):
        """
        `when' is a Unix time from Epoch.
        """
        with self.putcond:
            heapq.heappush(self.queue, (when, item))
            if when >= self.putwaketime and self.putwaketime != 0:
                return
            # Arm next timer.
            if self.timer is not None:
                self.timer.cancel()
            self.__arm()

    def get(self, timeout=None):
        """
        Timely return the next object on the queue.
        """
        with self.getcond:
            if len(self.triggered) > 0:
                when, item = self.triggered.popleft()
                return item
            self.getcond.wait(timeout)
            try:
                when, item = self.triggered.popleft()
            except IndexError:
                return None
            return item

    def qsize(self):
        """
        Self explanatory.
        """
        with self.putcond:
            return len(self.queue)

    def terminate(self):
        """
        Request the embedded thread to terminate.
        """
        with self.putcond:
            self.terminating = True
            if self.timer is not None:
                self.timer.cancel()
            self.putcond.notifyAll()


if __name__ == "__main__":
    q = TimelyQueue(0)
    N = 100000
    t0 = time.time()
    for i in range(N):
        q.put(time.time() + 2, i)
    dt = time.time() - t0
    print "put done in %.3fs (%.2f put/sec)" % (dt, N / dt)
    t0 = time.time()
    i = 0
    while i < N:
        a = q.get(3)
        if i == 0:
            dt = time.time() - t0
            print "start get after %.3fs" % dt
            t0 = time.time()
        i += 1
    dt = time.time() - t0
    print "get done in %.3fs (%.2f get/sec)" % (dt, N / dt)
    q.terminate()
    # Give change to the thread to exit properly, otherwise we may get
    # a stray interpreter exception.
    time.sleep(0.1)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM