Я пытаюсь обернуть голову, как я могу гарантировать, что счетчик ссылок на объекты является потокобезопасным.
class MyObject{
//Other implementation details
private:
mutable volatile LONGLONG * m_count;
IData * m_data;
};
Предположим, что есть необходимое объявление класса, просто сохраняя его простым. Вот реализация копирования конструктора и деструктора.
MyObject::MyObject(const MyObject& rhs) : m_count(rhs.m_count), m_data(rhs.m_data){
InterlockedIncrement64(m_count);
}
MyObject::~MyObject(){
if(InterlockedDecrement64(m_count) == 0)
delete m_data;
}
Эта тема безопасна? Как выглядит список использования конструктора копирования, атомарный или нет? Имеет ли это значение? Должен ли я устанавливать увеличенное значение счетчика в списке инициализации (возможно ли это)?
Это достаточно хорошо. Я думаю, что иначе, как я могу попасть в сценарий, где thread1
копирует и thread2
разрушает одновременно, когда count == 1
, Между потоками должно быть рукопожатие, означающее, что thread1 должен полностью скопировать объект, прежде чем объект thread2 выйдет из области видимости?
Прочитав некоторые из этих ответов, я вернулся и провел небольшое исследование. Boost реализует их shared_ptr очень похоже. Вот вызов деструктора.
void release() // nothrow
{
if( BOOST_INTERLOCKED_DECREMENT( &use_count_ ) == 0 )
{
dispose();
weak_release();
}
}
Некоторые предположили, что в документации Boost четко указано, что присваивание не является потокобезопасным. Я согласен и не согласен. Я думаю, что в моем случае я не согласен. Мне нужно только рукопожатие между нитью и нитью. Я не думаю, что некоторые из проблем, описанных в некоторых ответах, применимы здесь (хотя это были откровительные ответы, которые я до конца не продумал).
пример
ThreadA
ATACH (SharedObject); // Общий объект передается по значению, увеличивается число и т. Д. И т. Д.
ThreadB
// Принимает объект, добавляет его в список общих объектов. ThreadB находится на таймере, который уведомляет все SharedObjects о событии. Перед уведомлением копия списка защищается критическим разделом. CS выпущен, копии уведомлены.
ThreadA
отсоединить (SharedObject); // Удаляет общий объект из списка объектов
Теперь одновременно ThreadB скрывает SharedOjbect и уже сделал копию списка, прежде чем ThreadA отсоединит указанный общий объект. Все ок нет?
Технически это должно быть безопасно.
Зачем? Потому что для того, чтобы скопировать объект, источник должен иметь «ссылку», поэтому он не исчезнет во время копирования. Кроме того, никто не получает доступ к объекту, который в настоящее время находится в процессе строительства.
Деструктор тоже безопасен, так как в любом случае не осталось никаких ссылок.
Вы можете пересмотреть вопрос о подсчете ссылок. Эти ссылки на самом деле не существуют; каждому, имеющему ссылку на оригинал, придется каким-то образом уменьшать счет пересчета для копии, при условии, что он получил исходную ссылку до того, как была сделана копия. Копии должны начинаться как новые объекты со счетом 1.
РЕДАКТИРОВАТЬ: аналогичным образом, если вы реализуете оператор присваивания (который похож на копию в существующий объект), пересчет целевого объекта следует оставить как есть.
Конструктор небезопасен, потому что список инициализации не является атомарным (я не нашел ссылок на это в стандарте, но его вряд ли можно будет реализовать в любом случае).
Так что, если другой поток удалит объект, который в данный момент копируется текущим потоком — прямо между выполнением списка инициализации и InterlockedIncrement()
выполнение — вы получите сломанный (уже удален m_data
) m_data
а также m_count
, Это приведет как минимум к двойному удалению m_data
,
размещение InterlockedIncrement
в список инициализаторов не поможет, потому что переключение потоков может происходить после вызова ctor, но до m_count
инициализация.
Я не уверен, что возможно сделать это потокобезопасным без внешней блокировки (мьютекс или критическая секция). Вы могли бы по крайней мере проверить счетчик в ctor и создать исключение / создать «недействительный» объект, если он равен нулю, но это не очень хороший дизайн, я бы не рекомендовал его.
Этот код безопасен до тех пор, пока вызывающий код гарантирует, что объект, переданный по ссылке, не будет уничтожен во время выполнения этой функции. Это то же самое для любой функции, которая использует ссылку, и вам придется очень усердно работать, чтобы этого не делать.
Деструктор безопасен, потому что атомный декремент гарантированно равен нулю в одном и только одном потоке. И если это так, то должно быть так, что каждый другой поток уже завершил использование объекта и уже вызвал свою собственную операцию декремента.
Это предполагает, что все ваши взаимосвязанные операции имеют полный барьер.
как я могу попасть в сценарий, где thread1 копирует, а thread2 одновременно уничтожает, когда count == 1. Между потоками должно быть рукопожатие, означающее, что thread1 должен полностью скопировать объект, прежде чем объект thread2 выйдет из области действия правильно?
Вы не можете, пока каждый поток имеет свою собственную ссылку на объект. Объект не может быть уничтожен, когда thread1 копирует, если у thread1 есть собственная ссылка на объект. Thread1 не нужно копировать до того, как ссылка на thread2 исчезнет, потому что вы никогда не коснетесь объекта, если у вас нет ссылки на него.
Отдельные ссылки имеют слабую безопасность потоков и не должны быть доступны в разных потоках одновременно. Если два потока хотят получить доступ к одному и тому же объекту, каждый из них должен иметь свою собственную ссылку. При указании ссылки на объект в каком-либо другом коде (возможно, в другом потоке) выполните следующую последовательность операций:
Имейте свою собственную ссылку.
Создайте ссылку для другого кода из вашей собственной ссылки.
Теперь вы можете уничтожить свою ссылку или отдать другую ссылку.
Ваш конструктор копирования не является безопасным и не может быть безопасным.
Но вы можете безопасно использовать свой класс, если вы никогда не используете new / delete, а работаете только с объектами, созданными и уничтоженными автоматически (по объему).