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

Я имею 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.

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

_M_node = _M_node->_M_next;

Я предполагаю, что это - мчащееся условие. Список, возможно, изменился или даже очистился потоком 1, в то время как поток 2 выполнял итерации его.

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

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

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

Спасибо

10
задан red.clover 17 January 2010 в 03:41
поделиться

11 ответов

В целом использование контейнеров STL таким образом небезопасно. Вам придется реализовать специальный метод, чтобы обезопасить поток кода. Выбранное вами решение зависит от ваших потребностей. Скорее всего, я бы решил эту проблему, поддерживая два списка, по одному в каждом потоке. И сообщать об изменениях через свободную очередь lock free queue (упоминается в комментариях к этому вопросу). Вы также могли бы ограничить время жизни объектов Info, обернув их в boost::shared_ptr, например,

typedef boost::shared_ptr<Info> InfoReference; 
typedef std::list<InfoReference> InfoList;

enum CommandValue
{
    Insert,
    Delete
}

struct Command
{
    CommandValue operation;
    InfoReference reference;
}

typedef LockFreeQueue<Command> CommandQueue;

class Thread1
{
    Thread1(CommandQueue queue) : m_commands(queue) {}
    void run()
    {
        while (!finished)
        {
            //Process Items and use 
            // deleteInfo() or addInfo()
        };

    }

    void deleteInfo(InfoReference reference)
    {
        Command command;
        command.operation = Delete;
        command.reference = reference;
        m_commands.produce(command);
    }

    void addInfo(InfoReference reference)
    {
        Command command;
        command.operation = Insert;
        command.reference = reference;
        m_commands.produce(command);
    }
}

private:
    CommandQueue& m_commands;
    InfoList m_infoList;
}   

class Thread2
{
    Thread2(CommandQueue queue) : m_commands(queue) {}

    void run()
    {
        while(!finished)
        {
            processQueue();
            processList();
        }   
    }

    void processQueue()
    {
        Command command;
        while (m_commands.consume(command))
        {
            switch(command.operation)
            {
                case Insert:
                    m_infoList.push_back(command.reference);
                    break;
                case Delete:
                    m_infoList.remove(command.reference);
                    break;
            }
        }
    }

    void processList()
    {
        // Iterate over m_infoList
    }

private:
    CommandQueue& m_commands;
    InfoList m_infoList;
}   


void main()
{
CommandQueue commands;

Thread1 thread1(commands);
Thread2 thread2(commands);

thread1.start();
thread2.start();

waitforTermination();

}

Это не было скомпилировано. Вам все равно нужно убедиться, что доступ к вашим объектам Info является потокобезопасным.

4
ответ дан 3 December 2019 в 21:21
поделиться

См. пол, потолок и усечений в ANSI Common Lisp.

Примеры (см. Положительные и отрицательные числа):

CL-USER 218 > (floor -5 2)
-3
1

CL-USER 219 > (ceiling -5 2)
-2
-1

CL-USER 220 > (truncate -5 2)
-2
-1

CL-USER 221 > (floor 5 2)
2
1

CL-USER 222 > (ceiling 5 2)
3
-1

CL-USER 223 > (truncate 5 2)
2
1

используется обычно для разделения до целочисленного усечения.

-121--2490552-

Ваш для () цикл может потенциально держать замок для относительно длительного времени, в зависимости от того, сколько элементов это итерации. Вы можете попасть в реальные проблемы, если он «опросы» очереди, постоянно проверяя, стал ли новый элемент. Это делает нить собственной Mutex для неоправданно долгое время, давая несколько возможностей для нити производителя, чтобы сломаться и добавить элемент. И горящие много ненужных циклов ЦП в процессе.

Вам нужна «Ограниченная очередь блокировки». Не пишите это сами, дизайн блокировки не тривиален. Трудно найти хорошие примеры, большинство из них есть .NET код. Эта статья выглядит многообещающим.

5
ответ дан 3 December 2019 в 21:21
поделиться

Используйте array _ sum () для $ total _ rating _ points и $ total _ ratings .

-121--3545726-

Если мы откажемся от обсуждения особенностей и поговорим о стиле, я бы сказал, что это трубный стиль кодирования. Вы начинаете с некоторого объекта или коллекции, типоразмеров точек и свойств или точек и преобразования и делаете это до тех пор, пока не сформируете требуемый результат. Таким образом, легко написать цепочку преобразований, которые будут легко прочитать также. Признаки до некоторой степени также позволят применить тот же подход к построению типов.

-121--2529934-

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

Я бы серьезно воспринял мысль Матье . Многие проблемы, которые решаются с помощью многопоточного программирования, лучше решать с помощью передачи сообщений между потоками или процессами. Если вам нужна высокая пропускная способность и не требуется, чтобы обработка занимала одно и то же пространство памяти, попробуйте использовать что-то вроде интерфейса передачи сообщений (MPI) вместо развертывания собственного многопоточного решения. Существует множество реализаций C++ - OpenMPI , Boost.MPI , Microsoft MPI и т.д.

0
ответ дан 3 December 2019 в 21:21
поделиться

Вы должны использовать немного потоковой библиотеки. Если вы используете Intel TBB, вы можете использовать Concurrent_Vector или Concurrent_Queue. См. Это .

0
ответ дан 3 December 2019 в 21:21
поделиться

Вот моя проблема. В манифесте это было на уровне активности, а не на уровне приложения. android: label = «@ string/app _ name» >

-121--4196125-

Использовать коллекцию наблюдательных элементов. Он содержит события, отслеживающие изменение коллекции. Я считаю, что он в первую очередь используется для WPF, но я использовал его и с ASP.NET проектами.

-121--4860590-

Другие уже предложили безблокировочные альтернативы, поэтому я отвечу так, будто вы застряли, используя замки...

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

Жизнь была бы проще, если бы вы использовали очередь вместо списка и потребитель использовал синхронизированную очередь < Info >:: pop _ front () вместо итераторов, которые могут быть признаны недействительными за вашей спиной. Если потребителю действительно необходимо одновременно глотнуть фрагменты Info , используйте переменную условия , которая сделает потребителя блокированным до queue.size () > = minimum .

Библиотека Boost имеет удобную портативную реализацию переменных условий (которая даже работает с более старыми версиями Windows), а также обычную библиотеку многопоточности .

Для очереди производитель-потребитель, использующей (старомодную) блокировку, проверьте класс шаблона Очередь библиотеки ZThreads . Я не использовал ZThreads сам, беспокоясь об отсутствии последних обновлений, и потому, что он, похоже, не был широко использован. Тем не менее, я использовал его как вдохновение для раскатки своей собственной потоковой безопасной очереди производитель-потребитель (прежде чем я узнал о свободных очередях и TBB ).

Свободная от блокировки библиотека очереди/стека, похоже, находится в очереди проверки Boost. Будем надеяться, что мы увидим новый Boost.Lockfree в ближайшем будущем!:)

Если есть интерес, я могу написать пример блокировки очереди, которая использует std:: queue и Boost блокировки потоков.

EDIT :

Блог, на который ссылается ответ Рика, уже содержит пример очереди блокировки, в которой используются condvars std:: queue и Boost. Если ваш потребитель нуждается в глыбе кусков, вы можете расширить пример следующим образом:

void wait_for_data(size_t how_many)
    {
        boost::mutex::scoped_lock lock(the_mutex);
        while(the_queue.size() < how_many)
        {
            the_condition_variable.wait(lock);
        }
    }

Вы также можете изменить его, чтобы разрешить тайм-ауты и отмены.

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

1
ответ дан 3 December 2019 в 21:21
поделиться

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

1
ответ дан 3 December 2019 в 21:21
поделиться

В тексте описываются три решения для предотвращения прерывания соединения:

  1. Настройте последовательность соединения с помощью autoReconnect = true . Это свойство последовательности подключения URL-адреса, которая работает на уровне драйвера. Необходимо изменить последовательность подключения в конфигурации источника данных.

     url = «jdbc: mysql ://localhost: 3306/confluence? autoReconnect = true»
    
  2. Увеличьте время ожидания. Обычно это свойство базы данных. Вы можете увеличить это значение, чтобы увидеть, меньше ли прерываний соединения.

  3. Настройте пул соединений для проверки правильности соединения. Это делается в бассейне, а не на уровне водителя. Это будет зависеть от используемой реализации источника данных. Но он должен быть конфигурируемым в свойстве источника данных, если используется пул, например c3p0 .

Дополнительные комментарии:

  • Источник данных/пул также может иметь тайм-аут, соответствующий времени нахождения свободного соединения в пуле. Не путать с тайм-аутом базы данных.
  • Существует несколько способов проверки действительности соединения. Один из распространенных способов - иметь фиктивный тестовый стол. Пул выдаст выбор в фиктивной таблице тестов, чтобы убедиться, что соединение все еще в порядке.
-121--1090137-

Иногда BindableAttribute хорошо влияет на поведение привязки компонентов. Может быть, полезно запустить Reflector и найти «Атрибут» и просмотреть немного. Это зависит от вашего намерения, какие из них полезны.

-121--3007022-

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

Вы случайно удерживаете блокировку на 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);

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

-1
ответ дан 3 December 2019 в 21:21
поделиться

Лучший способ сделать это - использовать контейнер, который внутренне синхронизирован. TBB и Microsoft Concurrent_Queue делают это. Энтони Уильямс также имеет хорошую реализацию в своем блоге здесь

-
1
ответ дан 3 December 2019 в 21:21
поделиться

IE 6 поддерживает только псевдокласс : hover на ссылки, но IE 7 поддерживает его на большинстве элементов.

Как сказал Дэвид, это может не работать в режиме причуд. Тогда причиной будет то, что IE в основном возвращается к чему-то ближе к IE 4 в режиме причуд, позволяя много специфичных для IE функций и удаляя несколько совместимых со стандартами функций.

Если требуется функция : наведение на элемент блока и поддержка обратно в IE 6, можно использовать элемент ссылки и сделать его элементом блока с помощью CSS. Обратите внимание, что ссылка может содержать только встроенные элементы (например, no div s), так что если вы хотите блокировать элементы внутри ссылки, вам придется установить, что с помощью CSS также:

CSS:

.hoverlink { display: block; }
.hoverlink:hover { background: #eee; }
.hoverlink .item { display: block; }

HTML:

<a href="..." class="hoverlink">
  <span class="item">Line 1</span>
  <span class="item">Line 2</span>
  <span class="item">Line 3</span>
</a>

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

-121--4716575-

Как правило, использование контейнеров STL таким образом небезопасно. Для обеспечения безопасности потока кода необходимо внедрить конкретный метод. Выбор решения зависит от ваших потребностей. Я бы, наверное, решил это, сохранив два списка, по одному в каждом потоке. И передача изменений через блокирующую свободную очередь (упомянутую в комментариях к этому вопросу). Можно также ограничить время жизни объектов Info, заключив их в boost::shared_ptr, например,

typedef boost::shared_ptr<Info> InfoReference; 
typedef std::list<InfoReference> InfoList;

enum CommandValue
{
    Insert,
    Delete
}

struct Command
{
    CommandValue operation;
    InfoReference reference;
}

typedef LockFreeQueue<Command> CommandQueue;

class Thread1
{
    Thread1(CommandQueue queue) : m_commands(queue) {}
    void run()
    {
        while (!finished)
        {
            //Process Items and use 
            // deleteInfo() or addInfo()
        };

    }

    void deleteInfo(InfoReference reference)
    {
        Command command;
        command.operation = Delete;
        command.reference = reference;
        m_commands.produce(command);
    }

    void addInfo(InfoReference reference)
    {
        Command command;
        command.operation = Insert;
        command.reference = reference;
        m_commands.produce(command);
    }
}

private:
    CommandQueue& m_commands;
    InfoList m_infoList;
}   

class Thread2
{
    Thread2(CommandQueue queue) : m_commands(queue) {}

    void run()
    {
        while(!finished)
        {
            processQueue();
            processList();
        }   
    }

    void processQueue()
    {
        Command command;
        while (m_commands.consume(command))
        {
            switch(command.operation)
            {
                case Insert:
                    m_infoList.push_back(command.reference);
                    break;
                case Delete:
                    m_infoList.remove(command.reference);
                    break;
            }
        }
    }

    void processList()
    {
        // Iterate over m_infoList
    }

private:
    CommandQueue& m_commands;
    InfoList m_infoList;
}   


void main()
{
CommandQueue commands;

Thread1 thread1(commands);
Thread2 thread2(commands);

thread1.start();
thread2.start();

waitforTermination();

}

Это не было скомпилировано. Необходимо обеспечить безопасность доступа к объектам Info .

-121--3119897-

Я хотел бы знать, какова цель этого списка, тогда было бы проще ответить на вопрос.

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

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

Если товары стоят дорого, передайте указатели только во время коммуникации, вы избежите копирования самих предметов.

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

3
ответ дан 3 December 2019 в 21:21
поделиться

Чтобы предотвратить признание вашего итератора, вы должны заблокировать весь для петли . Теперь я думаю, что первый нить может иметь трудности обновлять список. Я бы попытался дать ему возможность выполнить свою работу на каждом (или каждой итерации NT).

В псевдо-коде, который будет выглядеть так:

mutex_lock();
for(...){
  doAction();
  mutex_unlock();
  thread_yield();  // give first thread a chance
  mutex_lock();
  if(iterator_invalidated_flag) // set by first thread
    reset_iterator();
}
mutex_unlock();
1
ответ дан 3 December 2019 в 21:21
поделиться

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

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

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

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


Вы работаете в C ++, и вы упомянули, нуждая в очередь с помощью POP-IF-NO-пустой . Я написал очереди двух замков много лет назад, используя библиотеку примитивов ACE примитивы параллелизма, что и библиотеки потоков , еще не готовы к производству, И шанс на стандартную библиотеку C ++, включая такие объекты, был далеким мечтом. Портировать его к чему-то более современному было бы легко.

Эта очередь шахты - называется одновременно :: DVE_LOCK_QUUEUE - позволяет получить доступ к главе очереди только через Raii. Это гарантирует, что приобретение блокировки для чтения головы всегда будет сопряжена с выпуском блокировки. Потребительские конструкции const_front (Const Access к элементу головы), спереди (без предельного доступа к элементу головы) или REVEWABLE_FRONT (не Const Доступ к элементам головы и преемника) Объект для представления исключительного права на доступ к главному элементу очереди. Такие «передние» объекты не могут быть скопированы.

Класс Two_Lock_Queue также предлагает функцию POP_FRONT () , которая ждет, пока хотя бы один элемент не будет удален, но в соответствии с STD :: queue S и STD :: Stack Стиль не смешивания мутации контейнера и копирования ценностей, POP_FRONT () возвращает void.

В компаньонном файле есть тип под названием Concurred :: undendional_pop , который позволяет проводить через Raii, что элемент головы очереди будет выскочин при выходе из текущего объема.

Файл Companion Error.hh Определяет исключения, которые возникают от использования функции Two_lock_Queue :: прерывание () , используется для разблокировки потоков, ожидающих доступа к головке очередь.

Посмотрите на код и дайте мне знать, если вам нужно больше объяснения, как его использовать.

1
ответ дан 3 December 2019 в 21:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: