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

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

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

Producer помещает указатель на данные в очередь соответствующих потоков (только указатель на данные, чтобы избежать копирования данных). Потоки заблокируют и отправят его через 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 Рекомендуется ли иметь переменную мьютекса 2Mil в одном отдельном процессе.
  2. Наличие большой единой структуры данных для поддержки InUseCounter приведет к увеличению времени блокировки для поиска и уменьшения
  3. Что было бы лучшей альтернативой моему подходу, чтобы выяснить ссылки, и у кого есть ссылки с очень меньшим временем блокировки.

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

3 ответа

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

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

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

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

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

  1. Да, 2000000 мьютексов - это перебор.
  2. 1 большая структура будет заблокирована дольше, но потребует гораздо меньше блокировок / разблокировок.
  3. Наилучшим подходом было бы использование интеллектуальных указателей shared_ptr: они, похоже, созданы специально для этого. Вы сами не проверяете счетчик, вы просто чистите указатель. shared_ptr является потокобезопасным, не для данных, на которые он указывает, но для 1 производителя (писателя) / N потребителя (читателей) это не должно быть проблемой.
Другие вопросы по тегам