Это вопрос из двух частей, касающихся атомарности std::shared_ptr
:
1.
Насколько я могу судить, std::shared_ptr
это единственный умный указатель в <memory>
этом атомарном. Мне интересно, есть ли std::shared_ptr
доступная неатомарная версия (я ничего не вижу <memory>
, поэтому я также открыт для предложений, выходящих за рамки стандарта, например, в Boost). Я знаю, что boost::shared_ptr
это тоже атомарно (если BOOST_SP_DISABLE_THREADS
не определено), но, может быть, есть другая альтернатива? Я ищу что-то с той же семантикой, что и std::shared_ptr
, но без атомарности.
2. Я понимаю, почему std::shared_ptr
атомарный; это довольно мило. Однако это подходит не для каждой ситуации, и в C ++ исторически существовала мантра «платите только за то, что вы используете». Если я не использую несколько потоков или если я использую несколько потоков, но не разделяю владение указателем между потоками, атомарный интеллектуальный указатель является излишним. Мой второй вопрос: почему std::shared_ptr
в С ++ 11 не была представлена неатомарная версия ? (при условии, что есть причина ) (если ответ просто «неатомарная версия просто никогда не рассматривалась» или «никто никогда не просил неатомарную версию», это нормально!).
Что shared_ptr
касается вопроса №2, мне интересно, предлагал ли кто-нибудь когда-нибудь неатомарную версию (либо для Boost, либо для комитета по стандартам) (не для замены атомарной версии shared_ptr
, а для сосуществования с ней), и она была сбита для конкретная причина.
источник
shared_ptr
значительно замедлялся из-за его атомарности, и определениеBOOST_DISABLE_THREADS
имело заметную разницу (я не знаю,std::shared_ptr
стоил бы такой же расходboost::shared_ptr
).shared_ptr
, не использует атомарные операции для refcount. См. (2) на gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html патч для GCC, позволяющий использовать неатомарную реализацию даже в многопоточных приложениях дляshared_ptr
объектов, которые не используются совместно потоки. Я сижу на этом патче много лет, но я подумываю о том, чтобы наконец сделать его для GCC 4.9Ответы:
Не предусмотрено стандартом. Он вполне может быть предоставлен «сторонней» библиотекой. Действительно, до C ++ 11 и до Boost казалось, что каждый написал свой собственный умный указатель с подсчетом ссылок (включая меня).
Этот вопрос обсуждался на встрече в Рапперсвиле в 2010 году. Этот вопрос был внесен в комментарий национального органа №20 Швейцарии. Обе стороны дискуссии приводили веские аргументы, в том числе те, которые вы привели в своем вопросе. Однако в конце обсуждения подавляющее большинство (но не единодушное) проголосовало против добавления несинхронизированной (неатомарной) версии
shared_ptr
.Аргументы против включены:
Код, написанный с помощью несинхронизированного shared_ptr, может в конечном итоге использоваться в многопоточном коде в будущем, что в конечном итоге вызовет проблемы с отладкой без предупреждения.
Наличие одного «универсального» shared_ptr, который является «односторонним» трафиком при подсчете ссылок, имеет преимущества: Из исходного предложения :
Стоимость атомики хоть и не нулевая, но не огромна. Стоимость снижается за счет использования конструкции перемещения и назначения перемещения, которые не требуют использования атомарных операций. Такие операции обычно используются при
vector<shared_ptr<T>>
стирании и вставке.Ничто не запрещает людям писать свои собственные неатомарные интеллектуальные указатели с подсчетом ссылок, если они действительно этого хотят.
Последнее слово рабочей группы в Рапперсвиле в тот день было:
источник
Has the same object type regardless of features used, greatly facilitating interoperability between libraries, including third-party libraries.
Это крайне странное рассуждение. Сторонние библиотеки в любом случае будут предоставлять свои собственные типы, так что какое это имеет значение, если они предоставили его в форме std :: shared_ptr <CustomType>, std :: non_atomic_shared_ptr <CustomType> и т. Д.? вам всегда придется адаптировать свой код к тому, что возвращает библиотека,std::shared_ptr<std::string>
где-нибудь взять . Если чья-то библиотека тоже принимает этот тип, вызывающие абоненты могут передавать одни и те же строки нам обоим без неудобств или накладных расходов на преобразование между разными представлениями, и это небольшая победа для всех.Ховард уже хорошо ответил на этот вопрос, и Никол сделал несколько хороших замечаний о преимуществах наличия единого стандартного типа общего указателя, а не множества несовместимых.
Хотя я полностью согласен с решением комитета, я действительно считаю, что использование несинхронизированного
shared_ptr
типа в особых случаях дает определенные преимущества , поэтому я исследовал эту тему несколько раз.В GCC, когда ваша программа не использует несколько потоков, shared_ptr не использует атомарные операции для refcount. Это выполняется путем обновления счетчиков ссылок с помощью функций-оберток, которые определяют, является ли программа многопоточной (в GNU / Linux это делается просто путем определения, связана ли программа с
libpthread.so
), и соответственно отправляют на атомарные или неатомарные операции.Много лет назад я понял, что, поскольку GCC
shared_ptr<T>
реализован в терминах__shared_ptr<T, _LockPolicy>
базового класса , можно использовать базовый класс с однопоточной политикой блокировки даже в многопоточном коде, явно используя__shared_ptr<T, __gnu_cxx::_S_single>
. К сожалению, поскольку это не было предполагаемым вариантом использования, он не работал оптимально до GCC 4.9, и некоторые операции по-прежнему использовали функции оболочки и поэтому отправлялись на атомарные операции, даже если вы явно запросили_S_single
политику. См. Пункт (2) на http://gcc.gnu.org/ml/libstdc++/2007-10/msg00180.html.для получения дополнительных сведений и исправления для GCC, позволяющего использовать неатомарную реализацию даже в многопоточных приложениях. Я сидел на этом патче много лет, но, наконец, сделал его для GCC 4.9, который позволяет вам использовать подобный шаблон псевдонима для определения типа общего указателя, который не является потокобезопасным, но работает немного быстрее:template<typename T> using shared_ptr_unsynchronized = std::__shared_ptr<T, __gnu_cxx::_S_single>;
Этот тип не будет взаимодействовать
std::shared_ptr<T>
и будет безопасным в использовании только в том случае, если гарантируется, чтоshared_ptr_unsynchronized
объекты никогда не будут совместно использоваться потоками без дополнительной синхронизации, предоставляемой пользователем.Это, конечно, совершенно непереносимо, но иногда это нормально. При правильном взломе препроцессора ваш код по-прежнему будет нормально работать с другими реализациями, если
shared_ptr_unsynchronized<T>
это псевдонимshared_ptr<T>
, с GCC он будет немного быстрее.Если вы используете GCC до 4.9, вы могли бы использовать это, добавив
_Sp_counted_base<_S_single>
явные специализации в свой собственный код (и гарантируя, что никто никогда не будет создавать экземпляры__shared_ptr<T, _S_single>
без включения специализаций, чтобы избежать нарушений ODR). Добавление таких специализацийstd
типов технически не определено, но будет работают на практике, потому что в этом случае нет никакой разницы между добавлением специализаций в GCC или их добавлением в ваш собственный код.источник
std::shared_ptr
,std::__shared_ptr
,__default_lock_policy
и тому подобное. Этот ответ подтвердил то, что я понял из кода.Так же легко можно спросить, почему нет навязчивого указателя или любого количества других возможных вариантов общих указателей, которые у вас могут быть.
Дизайн
shared_ptr
, переданный от Boost, заключался в создании минимального стандартного лингва-франка для интеллектуальных указателей. Что, вообще говоря, вы можете просто снять это со стены и использовать. Это то, что обычно используется в самых разных приложениях. Вы можете поместить его в интерфейс, и, скорее всего, хорошие люди захотят его использовать.В будущем потоки станут более распространенными. В самом деле, с течением времени многопоточность обычно становится одним из основных средств достижения производительности. Требование, чтобы базовый интеллектуальный указатель делал минимум, необходимый для поддержки потоковой передачи, упрощает эту реальность.
Было бы ужасно сбрасывать полдюжины интеллектуальных указателей с небольшими вариациями между ними в стандарт или, что еще хуже, в интеллектуальный указатель на основе политик. Каждый выберет указатель, который ему больше всего нравится, и откажется от всех остальных. Никто не сможет ни с кем общаться. Это будет похоже на текущие ситуации со строками C ++, где у каждого свой тип. Только намного хуже, потому что взаимодействие со строками намного проще, чем взаимодействие между классами интеллектуальных указателей.
Boost и, соответственно, комитет выбрали для использования конкретный умный указатель. Он обеспечивал хороший баланс функций и широко и повсеместно использовался на практике.
std::vector
имеет некоторую неэффективность по сравнению с голыми массивами в некоторых случаях. У него есть некоторые ограничения; некоторые пользователи действительно хотят иметь жесткое ограничение на размер avector
без использования распределителя распределения. Однако комитет не планировалvector
быть всем для всех. Он был разработан, чтобы использоваться по умолчанию для большинства приложений. Те, для кого это не работает, могут просто написать альтернативу, которая соответствует их потребностям.Так же, как и для умного указателя
shared_ptr
, атомарность - это бремя. С другой стороны, можно также подумать о том, чтобы не копировать их так часто.источник
Готовлю доклад на shared_ptr на работе. Я использовал модифицированный boost shared_ptr, избегая отдельного malloc (например, что делает make_shared) и параметра шаблона для политики блокировки, такого как shared_ptr_unsynchronized, упомянутого выше. Пользуюсь программой от
http://flyingfrogblog.blogspot.hk/2011/01/boosts-sharedptr-up-to-10-slower-than.html
в качестве теста, после очистки ненужных копий shared_ptr. Программа использует только основной поток, и отображается тестовый аргумент. Тестовый env - это ноутбук под управлением linuxmint 14. Вот время в секундах:
Только версия 'std' использует -std = cxx11, а -pthread, вероятно, переключает lock_policy в классе g ++ __shared_ptr.
По этим числам я вижу влияние атомарных инструкций на оптимизацию кода. В тестовом примере не используются контейнеры C ++, но
vector<shared_ptr<some_small_POD>>
он может пострадать, если объекту не нужна защита потока. Boost страдает менее вероятно, потому что дополнительный malloc ограничивает объем встраивания и оптимизации кода.Мне еще предстоит найти машину с достаточным количеством ядер для стресс-тестирования масштабируемости атомарных инструкций, но использование std :: shared_ptr только при необходимости, вероятно, лучше.
источник
Boost обеспечивает
shared_ptr
неатомарный. Он называетсяlocal_shared_ptr
, и его можно найти в библиотеке интеллектуальных указателей boost.источник
shared_ptr
равно со стойкой, хоть она и местная? Или вы имеете в виду, что с этим есть еще одна проблема? Документы говорят, что единственная разница в том, что это не атомарно.local_shared_ptr
иshared_ptr
они идентичны, за исключением атомарного. Мне искренне интересно узнать, правда ли то, что вы говорите, потому что я используюlocal_shared_ptr
в приложениях, требующих высокой производительности.