Как реализовать потокобезопасный подсчет ссылок в C++

Как реализовать эффективную и многопоточную систему подсчета ссылок на процессорах X86 на языке программирования C++?

Я всегда сталкиваюсь с проблемой, что критические операции не атомарны, и доступные операции блокировки X86 недостаточны для реализации системы подсчета ссылок.

Следующая статья охватывает эту тему, но требует специальных инструкций процессора:

http://www.ddj.com/architect/184401888

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, добавляет дополнительную сложность к учету ошибок при использовании умных указателей.

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

Другие вопросы по тегам