Есть ли неатомарный эквивалент std::shared_ptr? И почему в <memory>нет ни одного?
Это вопрос из двух частей, все об атомарности 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
предусмотрено в C++ 11? (при условии, что есть причина) (если ответ просто "неатомарная версия просто никогда не рассматривалась" или "никто никогда не просил неатомарную версию", это нормально!).
С вопросом № 2, мне интересно, если кто-то когда-либо предлагал неатомную версию shared_ptr
(либо для повышения, либо для комитета по стандартам) (не для замены атомарной версии shared_ptr
, но чтобы сосуществовать с ним) и его сбили по определенной причине.
4 ответа
1. Мне интересно, есть ли доступная неатомарная версия std::shared_ptr
Не предусмотрено стандартом. Вполне может быть один, предоставленный библиотекой "третьей стороны". Действительно, до C++11 и до Boost казалось, что каждый написал свой умный указатель с подсчетом ссылок (включая меня).
2. Мой второй вопрос: почему не была представлена неатомарная версия std::shared_ptr в C++11?
Этот вопрос обсуждался на совещании в Рапперсвиле в 2010 году. Этот вопрос был представлен в комментарии Национального органа № 20 Швейцарии. Были веские аргументы с обеих сторон дебатов, включая те, которые вы приводите в своем вопросе. Однако в конце обсуждения голосование было подавляющим (но не единодушным) против добавления несинхронизированной (неатомной) версии shared_ptr
,
Аргументы против включены:
Код, написанный с помощью несинхронизированного shared_ptr, может в конечном итоге использоваться в многопоточном коде в будущем, что в конечном итоге приводит к трудностям в отладке без предупреждения.
Наличие одного "универсального" shared_ptr, который является "односторонним" для трафика при подсчете ссылок, имеет преимущества: Из исходного предложения:
Имеет один и тот же тип объекта независимо от используемых функций, значительно облегчая взаимодействие между библиотеками, включая сторонние библиотеки.
Стоимость атомики, хотя и не равна нулю, не является подавляющей. Стоимость снижается за счет использования конструкции перемещения и назначения перемещения, в которых не нужно использовать атомарные операции. Такие операции обычно используются в
vector<shared_ptr<T>>
сотри и вставь.Ничто не запрещает людям писать свои собственные неатомовые умные указатели с подсчетом ссылок, если это действительно то, что они хотят делать.
Последнее слово от LWG в Рапперсвиле в тот день было:
Отклонить СН 20. Нет единого мнения, чтобы внести изменения в настоящее время.
Ховард уже хорошо ответил на этот вопрос, и Никол сделал несколько хороших замечаний о преимуществах наличия единого стандартного типа общего указателя, а не множества несовместимых.
Хотя я полностью согласен с решением комитета, я думаю, что использование несинхронизированных shared_ptr
-подобный тип в особых случаях, поэтому я изучил эту тему несколько раз.
Если я не использую несколько потоков или если я использую несколько потоков, но не разделяю владение указателем между потоками, атомарный интеллектуальный указатель является излишним.
С GCC, когда ваша программа не использует несколько потоков shared_ptr не использует атомарные операции для пересчета. Это делается путем обновления счетчиков ссылок с помощью функций-оболочек, которые определяют, является ли программа многопоточной (в 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 не представлена в C++11? (если есть причина).
Можно так же легко спросить, почему нет навязчивого указателя или любого другого числа возможных вариантов общих указателей, которые могут быть.
Конструкция shared_ptr, переданного от Boost, заключалась в создании минимального стандартного языка интеллектуальных указателей. Это, вообще говоря, вы можете просто снять это со стены и использовать его. Это то, что обычно используется в самых разных приложениях. Вы можете поместить его в интерфейс, и, скорее всего, хорошие люди захотят его использовать.
Потоки только станут более распространенными в будущем. Действительно, с течением времени многопоточность, как правило, будет одним из основных средств достижения производительности. Требование базового интеллектуального указателя для выполнения минимума, необходимого для поддержки многопоточности, облегчает эту реальность.
Сброс полдюжины умных указателей с незначительными отклонениями между ними в стандарт или, что еще хуже, умный указатель, основанный на политике, был бы ужасен. Каждый выберет указатель, который ему нравится больше всего, и откажется от всех остальных. Никто не сможет общаться с кем-либо еще. Это было бы похоже на текущую ситуацию со строками C++, где у каждого свой тип. Только намного хуже, потому что взаимодействие со строками намного проще, чем взаимодействие между классами интеллектуальных указателей.
Повысить, и, соответственно, комитет, выбрал определенный умный указатель для использования. Он обеспечивал хороший баланс функций и широко и широко использовался на практике.
std::vector
имеет некоторые недостатки по сравнению с голыми массивами в некоторых угловых случаях. Это имеет некоторые ограничения; некоторые виды использования действительно хотят иметь жесткое ограничение на размер vector
без использования метателя выделения. Тем не менее, комитет не проектировал vector
быть всем для всех. Он был разработан, чтобы быть хорошим по умолчанию для большинства приложений. Те, для кого это не может работать, могут просто написать альтернативу, которая соответствует их потребностям.
Так же, как вы можете для умного указателя, если атомарность shared_ptr является бременем. С другой стороны, можно также подумать не копировать их так много.
Повышение обеспечивает shared_ptr
это не атомно. Это называется local_shared_ptr
, и можно найти в библиотеке повышения указателей смарт-указателей.
Я готовлю доклад на shared_ptr на работе. Я использовал модифицированный буст 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. Вот время в секундах:
тестовый запуск установки boost(1.49) std с измененным бустом make_shared mt-unsafe(11) 11,9 9/11,5(-pthread on) 8,4 атомная (11) 13,6 12,4 13,0 mt-unsafe(12) 113,5 85,8/108,9(-поток включен) 81,5 атомная (12) 126,0 109,1 123,6
Только версия 'std' использует -std=cxx11, а -pthread, вероятно, переключает lock_policy в классе g++ __shared_ptr.
Из этих цифр я вижу влияние атомарных инструкций на оптимизацию кода. В тестовом примере не используются контейнеры C++, но vector<shared_ptr<some_small_POD>>
скорее всего пострадает, если объект не нуждается в защите потока. Boost страдает меньше, вероятно, потому что дополнительный malloc ограничивает количество встраивания и оптимизации кода.
Мне еще предстоит найти машину с достаточным количеством ядер, чтобы провести стресс-тестирование масштабируемости атомарных инструкций, но использование std::shared_ptr только при необходимости, вероятно, лучше.