Как реализовать потокобезопасный подсчет ссылок в C++
Как реализовать эффективную и многопоточную систему подсчета ссылок на процессорах X86 на языке программирования C++?
Я всегда сталкиваюсь с проблемой, что критические операции не атомарны, и доступные операции блокировки X86 недостаточны для реализации системы подсчета ссылок.
Следующая статья охватывает эту тему, но требует специальных инструкций процессора:
7 ответов
В настоящее время вы можете использовать умный указатель Boost/TR1 shared_ptr<> для хранения ссылок, подсчитанных ссылками.
Работает отлично; нет суеты, нет суеты. Класс shared_ptr<> заботится обо всей блокировке, необходимой для refcount.
В VC++ вы можете использовать _InterlockedCompareExchange.
do
read the count
perform mathematical operation
interlockedcompareexchange( destination, updated count, old count)
until the interlockedcompareexchange returns the success code.
На других платформах / компиляторах используйте соответствующую встроенную функцию для инструкции LOCK CMPXCHG, которую предоставляет _InterlockedCompareExchange от MS.
Строго говоря, вам нужно подождать, пока C++0x сможет написать потокобезопасный код на чистом C++.
На данный момент вы можете использовать Posix или создавать свои собственные независимые от платформы обертки для сравнения и замены и / или взаимосвязанного увеличения / уменьшения.
Win32 InterlockedIncrementAcquire и InterlockedDecrementRelease (если вы хотите быть в безопасности и заботиться о платформах с возможным переупорядочением, следовательно, вам нужно одновременно создавать барьеры памяти) или InterlockedIncrement и InterlockedDecrement (если вы уверены, что останетесь x86), являются атомарными и будут сделать работу.
Тем не менее, Boost/TR1 shared_ptr<> будет обрабатывать все это для вас, поэтому, если вам не нужно реализовать это самостоятельно, вы, вероятно, приложите все усилия, чтобы придерживаться этого.
Имейте в виду, что блокировка очень дорога, и она происходит каждый раз, когда вы перемещаете объекты между интеллектуальными указателями - даже когда объект в данный момент принадлежит одному потоку (библиотека интеллектуальных указателей этого не знает).
Учитывая это, здесь может применяться эмпирическое правило (я рад, что меня исправили!)
Если к вам применимо следующее:
- У вас есть сложные структуры данных, для которых было бы сложно написать деструкторы (или где семантика значений в стиле STL была бы неприемлемой по своему замыслу), поэтому вам нужны умные указатели, чтобы сделать это за вас, и
- Вы используете несколько потоков, которые разделяют эти объекты, и
- Вы заботитесь о производительности и правильности
... тогда фактическая сборка мусора может быть лучшим выбором. Хотя у GC плохая репутация по производительности, все относительно. Я считаю, что это очень выгодно по сравнению с блокировкой умных указателей. Это была важная часть того, почему команда CLR выбрала настоящий GC вместо того, чтобы использовать подсчет ссылок. Посмотрите эту статью, в частности, это абсолютное сравнение того, что означает справочное назначение, если у вас есть подсчет:
нет пересчёта:
a = b;
подсчет ссылок:
if (a != null)
if (InterlockedDecrement(ref a.m_ref) == 0)
a.FinalRelease();
if (b != null)
InterlockedIncrement(ref b.m_ref);
a = b;
Если сама инструкция не является атомарной, вам нужно сделать раздел кода, который обновляет соответствующую переменную, критическим разделом.
Тоесть вам нужно запретить другим потокам входить в этот раздел кода, используя некоторую схему блокировки. Конечно, блокировки должны быть атомарными, но вы можете найти механизм атомарной блокировки в классе pthread_mutex.
Вопрос эффективности: библиотека pthread настолько эффективна, насколько это возможно, и при этом гарантирует, что блокировка мьютекса является атомарной для вашей ОС.
Это дорого: наверное. Но для всего, что требует гарантии, есть стоимость.
Этот конкретный код, опубликованный в этой статье о dj, добавляет дополнительную сложность к учету ошибок при использовании умных указателей.
В частности, если вы не можете гарантировать, что интеллектуальный указатель не изменится при назначении другому интеллектуальному указателю, вы делаете это неправильно или делаете что-то очень ненадежное с самого начала. Если интеллектуальный указатель может измениться при назначении другому интеллектуальному указателю, это означает, что код, выполняющий назначение, не владеет интеллектуальным указателем, который, как подозревают, начинается с.