Существует ли неатомный эквивалент std:: shared_ptr? А почему нет в памяти <памяти>?

Это немного вопрос из двух частей, касающийся атомарности 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 является атомарным; это любопытно. Тем не менее, это не приятно для каждой ситуации, и на С++ исторически была мантра "только платить за то, что вы используете". Если я не использую несколько потоков, или если я использую несколько потоков, но я не разделяю права собственности на указатели на потоки, атомный умный указатель переполнен. Мой второй вопрос: почему не была неатомная версия std::shared_ptr, предоставленная в С++ 11? (при условии, что есть почему) (если ответ просто "неатомная версия просто никогда не рассматривалась" или "никто никогда не просил неатомную версию", это прекрасно!).

С вопросом № 2 мне интересно, предложил ли кто-нибудь неатомическую версию shared_ptr (либо в Boost, либо в комитете по стандартизации) (не для замены атомной версии shared_ptr, а для сосуществования с он), и он был сбит по определенной причине.

Ответ 1

1. Мне интересно, существует ли неатомная версия std:: shared_ptr

Не предоставляется стандартом. Там может быть предоставлена ​​библиотека "третьего лица". Действительно, до С++ 11 и до Boost казалось, что каждый написал свой собственный подсчитанный умный указатель (включая меня).

2. Мой второй вопрос: почему не была неатомная версия std:: shared_ptr, предоставленная в С++ 11?

Этот вопрос обсуждался на совещании Rapperswil в 2010 году. Этот вопрос был представлен Национальным органом № 20 Швейцарии. По обе стороны дискуссии были веские аргументы, в том числе те, которые вы предлагаете в своем вопросе. Однако в конце обсуждения голосование было подавляющим большинством (но не единогласным) против добавления несинхронизированной (неатомной) версии shared_ptr.

Аргументы против включенного:

  • Код, написанный с несинхронизированным shared_ptr, может в конечном итоге использоваться в потоковом коде по дороге, в результате чего возникают проблемы с отладкой без предупреждения.

  • Наличие одного "универсального" shared_ptr, который является "одним способом" для трафика в подсчете ссылок, имеет преимущества: от оригинального предложения:

    Имеет один и тот же тип объекта независимо от используемых функций, что значительно облегчает взаимодействие между библиотеками, включая сторонние библиотеки.

  • Стоимость атоматики, а не ноль, не является ошеломляющей. Стоимость снижается за счет использования конструкции перемещения и назначения перемещения, которые не требуют использования атомных операций. Такие операции обычно используются в стирании и вставке vector<shared_ptr<T>>.

  • Ничто не запрещает людям писать собственный интеллектуальный указатель, не содержащий атома, если это действительно то, что они хотят сделать.

Последнее слово из LWG в Rapperswil в тот день было:

Отклонить CH 20. В настоящее время нет согласия в отношении внесения изменений.

Ответ 2

Говард уже хорошо ответил на вопрос, и Ник отметил некоторые положительные моменты, связанные с преимуществами наличия единого стандартного общего типа указателя, а не множества несовместимых.

Хотя я полностью согласен с решением комитета, я думаю, что есть некоторая выгода от использования несинхронизированного типа shared_ptr -like в особых случаях, поэтому я исследовал эту тему несколько раз.

Если я не использую несколько потоков, или если я использую несколько потоков, но не разделяю владение указателем между потоками, атомарный интеллектуальный указатель является излишним.

С 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, и вы добавляете их в свой собственный код.

Ответ 3

Мой второй вопрос: почему не была атомная версия std:: shared_ptr, предоставленная в С++ 11? (предполагая, что есть почему).

Можно было бы также легко спросить, почему нет интрузивного указателя или любого количества других возможных вариантов общих указателей, которые можно было бы иметь.

Дизайн shared_ptr, переданный от Boost, заключался в создании минимальной стандартной лингва-франки интеллектуальных указателей. Это, вообще говоря, вы можете просто снять это с стены и использовать. Это то, что будет использоваться в целом, в самых разных приложениях. Вы можете поместить его в интерфейс, и шансы хорошие люди будут готовы использовать его.

Threading только станет более распространенным в будущем. Действительно, с течением времени потоки, как правило, являются одним из основных средств достижения производительности. Требование, чтобы основной умный указатель выполнял минимальный минимум, необходимый для поддержки потоков, облегчает эту реальность.

Сбрасывание полдюжины интеллектуальных указателей с небольшими вариациями между ними в стандарт или, что еще хуже, на основе умных указателей на основе политики было бы ужасно. Каждый выбирает указатель, который им больше нравится, и отказывается от всех остальных. Никто не сможет общаться с кем-либо еще. Это будет похоже на текущие ситуации со строками С++, где каждый имеет свой собственный тип. Только намного хуже, потому что взаимодействие со строками намного проще, чем взаимодействие между классами интеллектуальных указателей.

Boost и, в дополнение, комитет, выбрал специальный умный указатель для использования. Он обеспечил хороший баланс функций и широко использовался на практике.

std::vector имеет некоторую неэффективность по сравнению с голыми массивами в некоторых угловых случаях. Он имеет некоторые ограничения; некоторые из них действительно хотят иметь жесткий предел размера vector, не используя метатор-распределитель. Однако комитет не проектировал vector, чтобы быть всем для всех. Он был разработан, чтобы быть хорошим дефолтом для большинства приложений. Те, для кого он не может работать, могут просто написать альтернативу, которая отвечает их потребностям.

Так же, как вы можете для умного указателя, если shared_ptr атомарность - это бремя. Опять же, можно было бы также рассмотреть возможность не копировать их так много.

Ответ 4

Я готовлю разговор о 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. Вот время, затраченное секундами:

test run setup    boost(1.49)      std with make_shared     modified boost
mt-unsafe(11)         11.9         9/11.5(-pthread on)          8.4  
atomic(11)            13.6            12.4                     13.0  
mt-unsafe(12)        113.5         85.8/108.9(-pthread on)     81.5  
atomic(12)           126.0           109.1                    123.6  

Только версия 'std' использует -std = cxx11, а -pthread, вероятно, переключает lock_policy в класс g++ __shared_ptr.

Из этих чисел я вижу влияние атомных инструкций на оптимизацию кода. В тестовом примере не используются контейнеры С++, но vector<shared_ptr<some_small_POD>> может пострадать, если объект не нуждается в защите потока. Boost страдает менее вероятно, потому что дополнительный malloc ограничивает количество вложений и оптимизацию кода.

Мне еще предстоит найти машину с достаточным количеством ядер, чтобы подчеркнуть масштабируемость атомных инструкций, но использование std:: shared_ptr только тогда, когда это необходимо, вероятно, лучше.

Ответ 5

Boost предоставляет shared_ptr который не является атомарным. Он называется local_shared_ptr и может быть найден в библиотеке повышения указателей smart pointers.