Эффективная структура для многопоточного доступа - PullRequest
2 голосов
/ 03 сентября 2010

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

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

Теперь мне стало интересно, насколько эффективна будет C ++ STL Queue с точки зрения этого и с точки зрения необходимости снова искать тот же элемент при необходимости удалить его из очереди?

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

Кто-нибудь может посоветовать, как лучше всего реализовать это в многопоточной среде, чтобы структура не блокировалась в течение длительного времени, когда необходимо выполнить поиск?

Ответы [ 3 ]

1 голос
/ 03 сентября 2010

Вы можете сосредоточиться на том, что не самая сложная часть вашего дизайна здесь.

Если очередь FIFO без какой-либо расстановки приоритетов, то вашими аксессорами будут push_back () и pop_front () - очень быстро, даже если вы не столкнетесь с проблемой использования семантики сравнения и замены (CAS), но придерживаться простого мьютекса / критической секции. Если вам нужна возможность расставить приоритеты в трафике, все становится сложнее. Если вы все же используете блокировку CAS, то (в любом случае в Windows) вы не сможете улучшить элемент shared_mutex boost :: thread, не потратив слишком много времени на выполнение этой части кода. Не уверен насчет реализаций, отличных от Windows.

Более сложная часть этой проблемы, как правило, сигнализирует незанятым рабочим потокам о начале новой работы. Вы не можете иметь их зацикливание, пока queue.front () не будет пустым, поэтому вам нужен способ, чтобы обеспечить правильное количество свободных потоков, которые будут выбраны для постановки в очередь элементов. Когда рабочий поток переходит в режим ожидания, он может проверять наличие новой работы и, если это так, выполнять, если нет, то состояние очереди должно быть установлено в состояние ожидания, чтобы следующий push_back приводил к удару «пробуждение» для перезапуска пула рабочих потоков. Эта область должна быть на 100% устойчивой ко всем не смертельным исключениям, иначе ваш процесс станет темным.

Вы управляете своими собственными потоками или используете встроенный пул потоков? Планируете ли вы иметь пул потоков динамического размера или просто создавать N потоков (возможно, настраиваемых) и запускать их до завершения процесса?

Определенно, рабочие потоки должны вести процесс регистрации рабочего элемента. Понимание того, кто владеет рабочим элементом в любой части его жизненного цикла, жизненно важно. Остановите / начните работу, и краткое изложение рабочих мест и сроки будут полезны. если ведение журнала идет медленно, отправьте его в отдельный поток через очередь «забей и забудь», но тогда нужно следить за задержкой, что сделает твой журнал менее полезным. Если вам нужна возможность внешнего управления рабочими элементами в процессе работы, то хорошая идея - отделить структуру от очереди ожидающих работ - рабочие элементы, выполняемые в процессе выполнения, проиндексированные по потокам и показывающие текущее состояние / время запуска с отдельной блокировкой. Эта структура будет O (число потоков) настолько меньше, чем «ожидающая» очередь, поэтому сканирование вряд ли станет узким местом, если длительные результирующие операции выполняются вне блокировки структуры.

Относительно производительности - что будут делать ваши рабочие потоки? если рабочие элементы будут выполняться долго, выполнять много операций ввода-вывода или выполнять другие дорогостоящие операции, то взаимодействие с очередями не является вашим узким местом в производительности, поэтому чрезмерная оптимизация этой области является относительно непродуктивной. Учитывайте в своей конструкции всю систему, а не одну маленькую область.

Это только для начала. Удачи, это не простая система для надежного проектирования.

[РЕДАКТИРОВАТЬ] на основе описания рабочего элемента.

Синтаксический анализ должен быть быстрым (хотя может потребовать дорогостоящего поиска исходных данных - трудно сказать?), А доступ к БД - реже. Похоже, настройка DB может быть вашим самым большим ударом за доллар. Если у вас нет контроля над этим, то вам просто нужно максимально уменьшить медленную БД в своем проекте. Если у вас есть возможность сделать асинхронный доступ к БД, то рабочий поток может просто выполнить достаточно работы, чтобы начать вызов БД, а затем завершить работу с обратным вызовом, позволяя другой работе начать рабочий поток. Без асинхронного доступа к БД надежный тайм-аут запроса будет трудно реализовать без какого-либо альтернативного метода перенаправления, когда ваш основной рабочий поток не ожидает завершения вызовов БД. Вам необходимо отделить ваши основные рабочие потоки от зависимости от БД, если вы не можете доверять БД для своевременного возврата или ошибки. Может быть, какой-нибудь настраиваемый или специфичный для рабочего времени тайм-аут по запросу БД? Часто библиотеки API БД допускают это.

Ваш монитор тайм-аута должен знать о состоянии рабочего элемента.Возможно, какой-нибудь виртуальный метод Cancel () на вашем рабочем месте, чтобы обеспечить гибкость в очистке элементов с истекшим временем ожидания.

1 голос
/ 03 сентября 2010

Цитата Трава Саттер:

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

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

0 голосов
/ 03 сентября 2010

Взгляните на серию Herb Sutters " Effective Concurrency " (скоро она станет книгой).

Всегда удаляйте предметы из очереди перед употреблением - вы не едите яблокиВы все еще находитесь в дереве?

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

Также переместите запись в класс, выполняя обработку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...