Каков наилучший способ синхронизации доступа контейнера между несколькими потоками в приложении реального времени - PullRequest
10 голосов
/ 16 января 2010

У меня в приложении std::list<Info> infoList, который разделен между двумя потоками. Эти 2 темы обращаются к этому списку следующим образом:

Тема 1 : использует push_back(), pop_front() или clear() в списке (в зависимости от ситуации) Поток 2 : использует iterator для перебора элементов в списке и выполнения некоторых действий.

Поток 2 выполняет итерацию списка следующим образом:

for(std::list<Info>::iterator i = infoList.begin(); i != infoList.end(); ++i)
{
  DoAction(i);
}

Код скомпилирован с использованием GCC 4.4.2.

Иногда ++ i вызывает segfault и вылетает из приложения. Ошибка вызвана в строке 143 std_list.h в следующей строке:

_M_node = _M_node->_M_next;

Полагаю, это гоночное состояние. Список мог измениться или даже очиститься потоком 1, пока поток 2 его итерировал.

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

Мой вопрос такой: Поток 1 и поток 2 должны выполняться максимально быстро, поскольку это приложение в реальном времени. Что я могу сделать, чтобы предотвратить эту проблему и сохранить производительность приложения? Существуют ли алгоритмы без блокировок для решения такой проблемы?

Это нормально, если я пропускаю некоторые недавно добавленные Info объекты в итерации потока 2, но что я могу сделать, чтобы итератор не стал висящим указателем?

Спасибо

Ответы [ 12 ]

0 голосов
/ 16 января 2010

Вы должны использовать некоторую библиотеку потоков. Если вы используете Intel TBB, вы можете использовать concurrent_vector или concurrent_queue. Смотрите это .

0 голосов
/ 16 января 2010

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

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

Что-то вроде:

while (processItems) {
  Info item;
  lock(mutex);
  if (!infoList.empty()) {
     item = infoList.front();
     infoList.pop_front();
  }
  unlock(mutex);
  DoAction(item);
  delayALittle();
}

И функция вставки все равно должна выглядеть так:

lock(mutex);
infoList.push_back(item);
unlock(mutex);

Если очередь, вероятно, не будет большой, у меня будет соблазн использовать что-то вроде std::vector<Info> или даже std::vector<boost::shared_ptr<Info> >, чтобы минимизировать копирование объектов Info (при условии, что их копирование несколько дороже по сравнению to boost :: shared_ptr. Как правило, операции над вектором, как правило, выполняются немного быстрее, чем над списком, особенно если объекты, хранящиеся в векторе, маленькие и дешевые для копирования.

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