Следите за ссылкой на данные (сколько / кто) в многопоточности

Я столкнулся с проблемой многопоточности. Модель многопоточности - 1 производитель - N потребитель.

Производитель создает данные (символьные данные размером около 200 байт каждый), помещает их в кеш фиксированного размера (например, 2Mil). Данные не относятся ко всем потокам. Он применяет фильтр (настроенный) и определяет, что ни один из потоков не подходит для производимых данных.

Производитель помещает указатель на данные в очередь подходящих потоков (только указатель на данные, чтобы избежать копирования данных). Потоки будут удаляться из очереди и отправлять их по TCP/IP своим клиентам.

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

Возможный способ: использовать атомарную гранулярность. Когда производитель определяет количество подходящих потоков, он может обновить счетчик и список идентификаторов потоков.

class InUseCounter
{
    int           m_count;
    set<thread_t> m_in_use_threads;
    Mutex         m_mutex;
    Condition     m_cond;

public:
    // This constructor used by Producer
    InUseCounter(int count, set<thread_t> tlist)
    {
        m_count          = count;
        m_in_use_threads = tlist;
    } 

    // This function is called by each threads
    // When they are done with the data, 
    // Informing that I no longer use the reference to the data.
    void decrement(thread_t tid)
    {
        Gaurd<Mutex> lock(m_mutex);
        --m_count;
        m_in_use_threads.erease(tid);
    }

    int get_count() const { return m_count; }
};

мастер-чаш

map<seqnum, Data>
              |
              v
             pair<CharData, InUseCounter>

Когда производитель удаляет элемент, он проверяет счетчик, больше 0, он отправляет действие, чтобы освободить ссылку на потоки в наборе m_in_use_threads.

Вопрос

  1. Если в главном кеше есть записи 2Mil, будет равное количество InUseCounter, поэтому переменные Mutex, целесообразно ли иметь переменную mutex 2Mil в одном процессе.
  2. Наличие большой единой структуры данных для поддержки InUseCounter приведет к увеличению времени блокировки для поиска и уменьшения.
  3. Что было бы лучшей альтернативой моему подходу к поиску ссылок и у кого есть ссылки с очень меньшим временем блокировки.

Заранее спасибо вам за советы.


person naveenhegde    schedule 10.01.2012    source источник


Ответы (3)


  1. 2 миллиона мьютексов - это многовато. Даже если это легкие замки, они все равно несут некоторые накладные расходы.
  2. Помещение InUseCounter в единую структуру приведет к конфликту между потоками, когда они выпускают запись; если потоки не выполняются синхронно, этим можно пренебречь. Если они часто выпускают записи и уровень конкуренции возрастает, это, очевидно, снижает производительность.
  3. Вы можете повысить производительность, если один поток отвечает за ведение счетчиков ссылок на записи (поток-производитель), а другие потоки отправляют обратно события выпуска записи по отдельной очереди, превращая производителя в потребителя событий выпуска записи. Когда вам нужно сбросить запись, сначала обработайте все очереди релиза, а затем запустите логику релиза. Вам придется иметь дело с некоторой задержкой, так как теперь вы ставите события выпуска в очередь, а не пытаетесь обработать их немедленно, но производительность должна быть намного лучше.

Кстати, это похоже на то, как работает структура Disruptor. Это высокопроизводительная среда параллелизма Java(!) для высокочастотной торговли. Да, я сказал «высокопроизводительная Java» и «параллелизм» в одном предложении. Существует много ценных сведений о проектировании и реализации высокопроизводительного параллелизма.

person MSN    schedule 10.01.2012
comment
Ах, та же идея :) Аккуратная ссылка! - person Matthieu M.; 10.01.2012

Поскольку у вас уже есть очередь Producer->Consumer, одна очень простая система состоит в наличии очереди «обратной связи» (Consumer->Producer).

После потребления элемента потребитель передает указатель обратно производителю, чтобы производитель мог удалить элемент и обновить «свободный список» кэша.

Таким образом, только Производитель когда-либо касается внутренностей кэша, и никакой синхронизации там не требуется: нужно синхронизировать только очереди.

person Matthieu M.    schedule 10.01.2012

  1. Да, 2000000 мьютексов — это перебор.
  2. 1 большая структура будет заблокирована дольше, но потребует намного меньше запирания/разблокировки.
  3. лучшим подходом было бы использование интеллектуальных указателей shared_ptr: они, похоже, созданы специально для этого. Вы не проверяете счетчик самостоятельно, вы просто подчищаете свой указатель. shared_ptr является потокобезопасным, а не данными, на которые он указывает, но для 1 производителя (записи) / N потребителей (читателей) это не должно быть проблемой.
person stefaanv    schedule 10.01.2012
comment
Мы подумали о поддержке shared_ptr‹›. Проблема с таким подходом заключается в том, что потребитель долго удерживает ссылку. Может случиться всплеск памяти, также Producer теряет контроль над повреждением данных. Это может работать для небольшого набора данных, а время жизни объекта очень мало. - person naveenhegde; 10.01.2012
comment
@naveen: Потребитель должен хранить ссылку столько, сколько ему нужно. Если данные уничтожаются, когда потребитель все еще их использует, случаются плохие вещи. Если потребитель все еще удерживает ссылку после того, как это сделано, это плохой потребитель (и он также не будет устанавливать счетчик ссылок) - person stefaanv; 10.01.2012