Как реализовать потокобезопасный подсчет ссылок в C++
-
01-07-2019 - |
Вопрос
Как вы реализуете эффективная и потокобезопасная система подсчета ссылок на процессорах X86 на языке программирования C++?
Я всегда сталкиваюсь с проблемой, что критические операции не атомарные, а доступных операций блокировки X86 недостаточно для реализации системы подсчета ссылок.
В следующей статье рассматривается эта тема, но для этого требуются специальные инструкции ЦП:
Решение
В настоящее время вы можете использовать интеллектуальный указатель Shared_ptr<> Boost/TR1 для сохранения ссылок с подсчетом ссылок.
Прекрасно работает;никакой суеты, никакой суеты.Классshared_ptr<> заботится обо всех блокировках, необходимых для счетчика ссылок.
Другие советы
В VС++ вы можете использовать _InterlockedCompareExchange.
do
read the count
perform mathematical operation
interlockedcompareexchange( destination, updated count, old count)
until the interlockedcompareexchange returns the success code.
На других платформах/компиляторах используйте соответствующую встроенную функцию для инструкции LOCK CMPXCHG, которую предоставляет MS _InterlockedCompareExchange.
Строго говоря, вам придется дождаться выхода 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 настолько эффективна, насколько это возможно, и при этом гарантирует, что блокировка мьютекса является атомарной для вашей ОС.
Это дорого:Вероятно.Но за все, что требует гарантии, приходится платить.
Этот конкретный код, опубликованный в этой статье ddj, усложняет учет ошибок при использовании интеллектуальных указателей.
В частности, если вы не можете гарантировать, что интеллектуальный указатель не изменится при присвоении другому интеллектуальному указателю, вы делаете это неправильно или с самого начала делаете что-то очень ненадежное.Если интеллектуальный указатель может измениться во время присвоения другому интеллектуальному указателю, это означает, что код, выполняющий назначение, не владеет интеллектуальным указателем, что с самого начала является подозрительным.