C ++ Использование необработанного указателя на std :: shared_ptr - PullRequest
2 голосов
/ 02 апреля 2020

Итак, я пытаюсь передать общий указатель между потоками через механизм обмена сообщениями, который я использую. Из-за того, как работает сериализация / десериализация, я не могу напрямую встроить shared_ptr в сообщение, которое я отправляю. Поэтому мне нужно отправить необработанный указатель shared_ptr. См. Ниже:

Поток 1:

auto objPtr = std::make_shared<ObjectClass>();
uint64_t serializedPtr = reinterpret_cast<uint64_t>(&objPtr);

Поток 2:

std::shared_ptr<ObjectClass> objPtrT2 = *(reinterpret_cast<std::shared_ptr<ObjectClass>*>(serializedPtr));

Это иногда приводит к сбою в потоке 2 при увеличении счетчика ссылок общего ресурса указатель. Я могу только предположить, что здесь участвуют некоторые условия гонки, но я не смог найти решение. Обратите внимание, что он не всегда обрабатывает sh, и десериализация, по-видимому, всегда успешна.

Нужно ли синхронизировать доступ к этому shared_ptr (самому shared_ptr, а не тому, на что указывает share_ptr)? Я обеспокоен тем, что способ, которым я передаю этот shared_ptr, нарушает способ управления счетчиком ссылок.

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

Спасибо

РЕДАКТИРОВАТЬ: Просто отметить, что поток 1 и поток 2 находятся в одном процессе / хосте. Я включаю shared_ptr в карту, управляемую thread1 (я пытался опустить детали, которые я изначально считал неважными). Однако я не осознавал, что способ, которым я получаю данные с указанной карты, был неправильным. Я копировал содержимое карты во временный shared_ptr и затем отправлял адрес временного shared_ptr в thread2. Поэтому я невольно отправлял адрес переменной в стеке. Глупая ошибка, но я думаю, что комментарий в этой теме был довольно поучительным / полезным, тем не менее. Кажется, следующее исправляет мою проблему.

auto& objPtr = m_objMap[index];
uint64_t serializedPtr = reinterpret_cast<uint64_t>(&objPtr);

Ответы [ 2 ]

4 голосов
/ 02 апреля 2020

shared_ptr автоматически увеличивает и уменьшает свой внутренний счетчик ссылок, когда вы копируете его (используя присвоение operator=), и, что важно, - когда shared_ptr уничтожается (выходя из области видимости). Ваш подход для передачи указателя на совместно используемый указатель в корне ошибочен в приведенном выше коде, поскольку вы передаете адрес временного общего указателя, но не владение или срок жизни, shared_ptr, который все еще принадлежит потоку A, может go выйти из области видимости и быть уничтоженным, прежде чем поток B сможет его использовать.

Чтобы передать владение экземпляра shared_ptr, я бы предложил создать динамически распределенную / динамически c shared_ptr для передачи. Это можно сделать, используя new или (еще лучше) make_unique. Используя unique_ptr (то есть: unique_ptr<shared_ptr<ObjectClass>>), вы должны использовать метод release, прежде чем передавать указатель через барьер потока в вашем сообщении.

Поток A:

auto sharedPtr = std::make_shared<ObjectClass>();

// This line creates a heap-allocated copy of a 
// shared_ptr (incrementing reference count)
// And places ownership inside a unique_ptr
auto sharedPtrDynamicCopy = std::make_unique<decltype(sharedPtr)>(sharedPtr);

// This 'releases' ownership of the heap-allocated shared_ptr,
// returning a raw pointer; it is now a potential
// memory leak!!!  It must be 'captured' in Thread B.
auto rawPtrToPass = sharedPtrDynamicCopy.release();

Поток B:

// Here, we immediately 'capture' the raw pointer back
// inside a unique_ptr, closing the loop on the potential
// memory leak
auto sharedPtrDynamicCopy = unique_ptr<shared_ptr<ObjectClass>>(rawPtrFromThreadA);

// Now we can make a copy of the shared_ptr, if we like.
// This sharedCopy will live on, even after recvdPtr goes
// out of scope.
auto sharedCopy = *sharedPtrDynamicCopy;

Возможно, вы могли бы сократить это далее, просто «добавив новый» shared_ptr вместо захватывая его внутри unique_ptr<shared_ptr<T>>, но я лично предпочитаю этот подход, поскольку он имеет четкую семантику «захват» и «выпуск» для указателя в полете.

2 голосов
/ 02 апреля 2020

Ваш код отправляет адрес временного общего указателя из одного потока в другой. Вы создаете objPtr как объект в стеке и передаете поток objPtr (не адрес объекта!) В поток.

Это не то, что вы хотите сделать. У вас есть два правильных варианта:

  1. Отправьте адрес объекта из одного потока в другой и убедитесь, что хотя бы один общий указатель на объект существует вообще. раз (как вы утверждаете, ваш код уже делает). Это страшное исправление, поскольку оно не обеспечивает достаточную продолжительность жизни объекта.

  2. Используйте new для динамического выделения дополнительного общего указателя на объект и передачи указателя на этот общий указатель на поток. У потока delete общий указатель, когда он будет завершен с объектом. Вероятно, это самое простое исправление, обеспечивающее достаточный срок службы объекта.

Общий шаблон для передачи объектов в потоки состоит в создании класса или структуры, которая несет всю информацию, которую вы хотите передать. в тему. Перед созданием потока используйте new, чтобы создать новый экземпляр структуры и заполнить его по мере необходимости. Затем передайте адрес этой структуры в поток. Когда поток завершен со структурой, используйте поток delete, чтобы освободить структуру. Вы можете использовать это для передачи любой коллекции объектов или данных в поток. В этом случае вы хотите передать фактический shared_ptr потоку, а не его адрес.

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