priority_queue с динамическими приоритетами

У меня есть серверное приложение, которое принимает входящие запросы и выполняет их. Если запросов слишком много, они должны быть поставлены в очередь, и если некоторые другие запросы были выполнены, запросы в очереди также должны быть выполнены. Поскольку я хочу передавать запросы с разными приоритетами, я думаю, что использование priority_queue будет лучшим выбором.

например Количество принимаемых запросов (a) достигает предела, и новые запросы будут храниться в очереди. Все запросы имеют приоритет 1 (самый низкий), если некоторые из запросов из (а) будут выполнены, программа выберет запрос с наивысшим приоритетом из очереди и выполнит его. Все еще нет проблем. Теперь кто-то отправляет запрос с приоритетом 5, который добавляется в очередь. Поскольку это запрос с наивысшим приоритетом, приложение выполнит этот запрос, как только количество запущенных запросов перестанет достигать предела. В худшем случае может быть, что 500 запросов с приоритетом 1 поставлены в очередь, но не будут выполняться, поскольку кто-то всегда отправляет запросы с приоритетом 5, поэтому эти 500 запросов будут стоять в очереди на долгое время. Чтобы предотвратить это, я хочу увеличить приоритет всех запросов, которые имеют более низкий приоритет, чем запрос с более высоким приоритетом, в этом примере, которые имеют приоритет ниже 5. Итак, если запрос с приоритетом 5 будет вытащен очереди все остальные запросы с приоритетом ‹ 5 должны быть увеличены на 0,2. Таким образом, запросы с низким приоритетом не будут вечно стоять в очереди, даже если может быть 100 запросов с более высоким приоритетом.

Я очень надеюсь, что кто-то может помочь мне решить проблему с приоритетами:

Поскольку мои запросы состоят из объекта, я подумал, что может работать что-то вроде этого:

class Query {
public:
    Query( std::string p_stQuery ) : stQuery( p_stQuery ) {};
    std::string getQuery() const {return stQuery;};
    void increasePriority( const float fIncrease ) {fPriority += fIncrease;};

    friend bool operator < ( const Query& PriorityFirst, const Query& PriorityNext ) {
        if( PriorityFirst.fPriority < PriorityNext.fPriority ) {
            if( PriorityFirst.fStartPriority < PriorityNext.fStartPriority ) {
                Query qTemp = PriorityFirst;
                qTemp.increasePriority( INCREASE_RATE );
            }

            return true;
        } else {
            return false;
        }
    };

private:
    static const float INCREASE_RATE = 0.2;
    float fPriority; // current priority
    float fStartPriority; // initialised priority
    std::string stQuery;
};

person Community    schedule 27.05.2010    source источник


Ответы (6)


Сколько дискретных значений приоритета вы хотите реализовать? Если их количество невелико (скажем, 256 уровней), то вместо одной приоритетной очереди имеет смысл иметь 256 простых очередей (так в некоторых ОС реализованы планировщики приоритетных процессов). Изначально ваши события, отправленные с приоритетом 1, помещаются в очередь №1. Затем кто-то отправляет событие prio=25, и оно помещается в очередь №25. Читатель считывает событие из самой высокой непустой очереди (в данном случае № 25), а события во всех непустых очередях до 25 перемещаются вверх по слоту (вся очередь № 1 перемещается в очередь № 2 и т. д.). . Я почти уверен, что все это можно сделать за O(k) (где k — количество уровней приоритета), либо с помощью std::move, либо с помощью std::swap, либо с помощью list::splice.

Перемещение очереди № 1 в позицию, ранее занятую очередью № 2, должно быть O (1) с перемещением или обменом, и если остаток очереди prio = 25 не пуст, то перемещение всех элементов вверх из очереди № 24 в очередь № 25. равно O(1), если используются очереди std::list и list::splice().

person Cubbi    schedule 27.05.2010
comment
Очень интересная идея. Я бы не стал использовать список здесь, скорее vector из deque. Также рассмотрим крайний случай: извлечение элемента с приоритетом 25 не означает, что эта очередь пуста, поэтому вам нужно объединить очередь 24 в конец очереди 25. Затем очистить 24, а затем поменять местами 24‹-›23, 23‹-›22 и т.д.. 1‹--›0. Конкатенация может быть длинной (в зависимости от размера), но все остальные операции — это простые присваивания указателей, поэтому они будут быстрыми. - person Matthieu M.; 27.05.2010
comment
256 дискретных значений приоритета определенно будут максимальными. Мне очень нравится эта идея, но я немного беспокоюсь о движениях и производительности... O(n) будет в порядке. - person ; 27.05.2010
comment
Упс, я хотел написать O(1), а не O(n), извините. list::splice - это постоянное время. - person Cubbi; 27.05.2010
comment
Или, если быть точным, O(k), где k — количество уровней приоритета. - person Cubbi; 27.05.2010

std::priority_queue не имеет возможности реорганизовать себя, если ваши критерии сортировки изменятся. Вам придется управлять им самостоятельно.

Я бы предложил просто использовать std::vector и один из двух подходов для его обслуживания.

Есть два случая: если вы меняете приоритеты чаще, чем удаляете элементы (что, похоже, не так), просто не сортируйте их и используйте min_element, чтобы найти элемент, который нужно удалить, когда это необходимо.

В противном случае, вероятно, лучше использовать подход Кристо и поддерживать свою собственную кучу. Когда вы меняете приоритет, вызовите make_heap, чтобы добавить, используйте push_heap, а чтобы получить верхний элемент, используйте pop_heap.

person Mark B    schedule 27.05.2010
comment
Если вы увеличиваете приоритет каждого элемента так, что порядок не меняется (вы увеличиваете их на ту же величину или до фиксированного максимума), вы не изменили инвариант кучи и не должны заново создавать куча. - person Andrew Aylett; 27.05.2010
comment
@andrew, ты должен предоставить это в качестве ответа - person deft_code; 27.05.2010
comment
@Andrew Если запрос с более низким приоритетом, скажем, 4,9, а запрос с более высоким приоритетом - 5,0, и я увеличиваю приоритет на 0,2, то порядок на самом деле меняется (когда я читаю ОП, только элемент с более низким приоритетом получает увеличение приоритета). - person Mark B; 27.05.2010

Если вам нужно изменить приоритеты, вы не можете использовать priority_queue, потому что нет интерфейса для доступа ко всем элементам. Лучше с std::vector и std::make_heap.

person Michael Kristofik    schedule 27.05.2010

Структура ACE, вероятно, предоставляет что-то, что могло бы вам помочь. См. классы ACE_Dynamic_Message_Queue и ACE_Laxity_Message_Strategy. Я еще не работал с этими классами, поэтому не могу привести пример. Но идея такова:

  • У вас есть очередь сообщений, совместно используемая двумя потоками
  • В потоке производителя вы заполняете очередь сообщений
  • Очередь сообщений будет вставлять новые сообщения в нужное место в соответствии со стратегией.
  • В потоке получателя вы только что прочитали самый верхний элемент из очереди.
person jopa    schedule 27.05.2010

Прежде всего, некоторые комментарии к вашему коду:

  1. Вы не можете гарантировать, что оператор ‹ будет вызываться только один раз для каждого объекта при каждом удалении (его можно вызывать как вверху, так и при извлечении, или при нажатии, или...).
  2. Вы повышаете приоритет локальной копии в операторской функции, а не копии в очереди
  3. Здесь нет необходимости использовать дружественные функции для объявления оператора‹.

Я написал пример, который переопределяет priority_queue для конкретной очереди, которую вы хотите, я надеюсь, что вы можете продолжить отсюда. Поведение должно быть реализовано в очереди, а не в классе Query, это только должно предоставить необходимые средства доступа, чтобы разрешить это поведение. Класс Query не должен знать об очереди.

В основном он копирует размер и пустую обычную очередь и создает 2 новых метода для вставки и получения запросов (push, pop и top отключены). Вставьте только вызовы push, получите вызовы как top, pop и обновите все приоритеты, используя вызов for_each в локальном контейнере. Наконец, предоставляется небольшая программа, показывающая функциональность.

Кроме того, он основан на управлении кучей в pop и push. Насколько я знаю, это будет работать правильно из-за линейного изменения каждого элемента, порядок не меняется между нажатиями;).

#include <algorithm>
#include <queue>
#include <iostream>

class Query
{
public:
    Query( std::string p_stQuery, double priority = 1.0) : stQuery( p_stQuery ) , fPriority(priority), fStartPriority(priority) {};
    std::string getQuery() const
    {
        return stQuery;
    };
    void conditionalIncrease( const Query& otherQuery )
    {
        if (fStartPriority < otherQuery.fStartPriority) fPriority += INCREASE_RATE;
    }

    bool operator < ( const Query& rhs )  const
    {
        return fPriority < rhs.fPriority;
    }

    double getPriority() const
    {
        return fPriority;
    }
private:
    static const double INCREASE_RATE = 0.2;
    double fPriority; // current priority
    double fStartPriority; // initialised priority
    std::string stQuery;
};

class QueryIncreaser
{
private:
    Query base;
public:
    QueryIncreaser(const Query& q) : base(q) {}
    void operator() (Query& q)
    {
        q.conditionalIncrease(base);
    }
};

class QueryQueue : private std::priority_queue<Query>
{
public:
    void insertQuery(const Query& q)
    {
        push(q);
    }
    Query getQuery()
    {
        Query q = top();
        pop();
        QueryIncreaser comp(q);
        for_each(c.begin(), c.end(), comp);
        return q;
    }
    bool empty() const
    {
        return std::priority_queue<Query>::empty();
    }
    size_t size() const
    {
        return std::priority_queue<Query>::size();
    }
};

int main ()
{
    Query a("hello"), b("world",2.);
    QueryQueue myQueue;
    myQueue.insertQuery(a);
    myQueue.insertQuery(b);
    while (!myQueue.empty())
    {
        std::cout << myQueue.getQuery().getPriority() << std::endl;
    }
    return 0;
}
person KillianDS    schedule 27.05.2010
comment
Вы должны использовать композицию вместо наследования. Кроме того, я не уверен только в увеличении запросов с более низким приоритетом: обычно вы выдаете запрос с наивысшим приоритетом, так что... это несколько упростит код :) - person Matthieu M.; 27.05.2010
comment
@Matthieu, если бы в очереди было несколько элементов с наивысшим приоритетом, я думаю, вам потребовалась бы проверка меньше, чем проверка. Вы все еще можете остановиться после того, как наткнетесь на предмет, не меньше, чем хотя. - person Mark B; 27.05.2010
comment
Хороший звонок! И сказать, что я сделал то же самое замечание по поводу другого ответа... сегодня моему мозгу не хватает последовательности. - person Matthieu M.; 27.05.2010
comment
@Matthieu, то, что я опубликовал, невозможно сразу с композицией (посмотрите лучше на метод getQuery). Однако я согласен с тем, что если вам действительно нужна производительность, вам, вероятно, будет лучше с собственной реализацией кучи. И действительно, код, вероятно, можно сделать немного быстрее, избегая for_each :). - person KillianDS; 28.05.2010
comment
Я не вижу в getQuery ничего такого, чего нельзя было бы добиться с помощью композиции... но, возможно, я что-то упускаю, я немного приболел. - person Matthieu M.; 28.05.2010

Я бы пошел с std::deque и собрал все остальное самостоятельно (если вы просто используете C++ без внешних библиотек, которые могут помочь). Проблема с другими предложениями make_heap (которые использует std::priority_queue) заключается в том, что они нестабильны. Отсутствие стабильности в данном случае означает, что упорядочение не гарантируется в пределах приоритета и возможно голодание.

person stonemetal    schedule 27.05.2010