Отслеживайте ссылки на данные (сколько / кого) в многопоточности
Я столкнулся с проблемой в многопоточности, модель многопоточности - 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.
Вопрос
- Если в главном кэше есть записи 2Mil, будет одинаковое количество InUseCounter, поэтому переменные Mutex Рекомендуется ли иметь переменную мьютекса 2Mil в одном отдельном процессе.
- Наличие большой единой структуры данных для поддержки InUseCounter приведет к увеличению времени блокировки для поиска и уменьшения
- Что было бы лучшей альтернативой моему подходу, чтобы выяснить ссылки, и у кого есть ссылки с очень меньшим временем блокировки.
Заранее спасибо за советы.
3 ответа
- 2 миллиона мьютексов это немного много. Даже если они легкие замки, они все еще занимают некоторые накладные расходы.
- Ввод
InUseCounter
в одной структуре может возникнуть конфликт между потоками, когда они выпускают запись; если потоки не выполняются в режиме блокировки, это может быть незначительным. Если они часто выпускают записи, и уровень конкуренции увеличивается, это, очевидно, снижение производительности. - Вы можете повысить производительность, если один поток будет отвечать за поддержание счетчиков ссылок на записи (поток производителя), а другие потоки будут отправлять события выпуска записи обратно в отдельную очередь, фактически превращая производителя в потребителя события выпуска записи. Когда вам нужно очистить запись, сначала обработайте все очереди выпуска, а затем запустите логику выпуска. Вам придется иметь дело с некоторой задержкой, так как теперь вы ставите в очередь события релиза, а не пытаетесь обрабатывать их немедленно, но производительность должна быть намного лучше.
Кстати, это похоже на то, как работает структура Disruptor. Это высокопроизводительная среда параллелизма Java(!) Для высокочастотной торговли. Да, я сказал высокую производительность Java и параллелизм в одном предложении. Существует много ценных знаний о проектировании и реализации высокопроизводительного параллелизма.
Так как у вас уже есть Producer->Consumer
очередь, одна очень простая система состоит в том, чтобы иметь очередь "обратной связи" (Consumer->Producer
).
После использования элемента потребитель передает указатель обратно Производителю, чтобы Производитель мог удалить элемент и обновить "свободный список" кэша.
Таким образом, только Производитель когда-либо прикасается к внутренностям кэша, и никакой синхронизации там не требуется: синхронизировать нужно только очереди.
- Да, 2000000 мьютексов - это перебор.
- 1 большая структура будет заблокирована дольше, но потребует гораздо меньше блокировок / разблокировок.
- Наилучшим подходом было бы использование интеллектуальных указателей shared_ptr: они, похоже, созданы специально для этого. Вы сами не проверяете счетчик, вы просто чистите указатель. shared_ptr является потокобезопасным, не для данных, на которые он указывает, но для 1 производителя (писателя) / N потребителя (читателей) это не должно быть проблемой.