Могу ли я заблокировать элемент в списке, чтобы сделать его потокобезопасным, вместо блокировки всего списка?

#c #multithreading #thread-safety

#c #многопоточность #Потокобезопасность

Вопрос:

Я бы сохранял объекты в моем std-списке потокобезопасным способом. Я не хочу блокировать поток, если он хочет получить доступ к элементу, который в данный момент не используется. Если я не могу заблокировать только один элемент, возникает другой вопрос: если я вызову деструктор для одного из моих объектов в списке, удалит ли он элемент и сделает недействительным его итератор?

Комментарии:

1. Не могли бы вы кратко объяснить, зачем вам это нужно (что вы делаете), каково время жизни объектов в списке и как потоки работают со списком (например, перебирают весь или что-то еще, и как они узнают, с каким элементом работать)?

Ответ №1:

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

Если вы сохраняете объекты new’ed в своем контейнере, то деструктор будет вызван только тогда, когда вы удаляете указатель на объект.

Дальнейший ответ на ваш вопрос зависит от:

1. Что вы храните в списке? Сам объект или указатель на объект

2. «Если я вызову деструктор для одного из моих объектов в списке»… Означает ли это удаление узла списка или извлечение указателя из списка, а затем удаление его вместо удаления узла списка [Это нехорошо, если вы не укажете указатель на NULL] .

Если вы сохраняете сам объект, list.remove вызовет деструктор для объекта.

Если вы сохраняете указатель на объект .. list.remove не вызовет деструктор, и я думаю, что это также не-операция [поправьте меня, если я здесь ошибаюсь]

3. Стандарт гарантирует, что итератор списка не будет признан недействительным при добавлении и объединении… но станет недействительным, если итератор, указывающий на удаленный элемент.

Комментарии:

1. «Если вы сохраняете объекты new’ed в своем контейнере, тогда деструктор будет вызван только тогда, когда вы удаляете указатель на объект». Я это знаю. Но что происходит, когда я вызываю деструктор вручную?

2. @Alexander, прямой вызов деструкторов, как правило, не такая уж хорошая идея. В вашем примере, если вы сделаете это, вы освободите память, но список останется неизменным с записью в этот объект (теперь освобожденный), так что в конечном итоге вы получите неопределенное поведение. Не очень хорошая идея.

3. Спасибо за ответ, а также за отличный комментарий муррекатта.

4. @murrekatt: «если вы [вызовете деструктор вручную], вы освободите память» — нет. Для иллюстрации предположим, что list содержит указатель на объект, который является struct членом const char* , на строку ASCIIIZ, выделенную из кучи. Деструктор вполне может освободить строку ASCIIIZ, но память, используемая самим объектом, не будет освобождена простым вызовом его деструктора. Если размещение new немедленно используется для создания другого объекта (того же типа) в том же местоположении, поведение не будет неопределенным. Но вы правы — это не очень хорошая идея: слишком привередливая.

5. @Tony, я не совсем понимаю твою точку зрения. Если в списке есть указатели на структуру std::list<MyStruct *> и кто-то вызывает деструктор одного из них, память объекта освобождается (при условии, что он может уничтожить себя должным образом). В списке по-прежнему будет указатель на это местоположение, но он будет висячим. И да, если кто-то создаст новый (тот же) объект в том же месте, куда указывает указатель списка, на вас действительно не будет иметь неопределенного поведения. Может быть, я пропустил, что вы имели в виду?

Ответ №2:

Могу ли я заблокировать элемент в списке, чтобы сделать его потокобезопасным, вместо блокировки всего списка?

Да, если все потоки используют блокировку при чтении / записи / уничтожении элемента, то это безопасно.

Например, если элемент был string , и один поток принял блокировку, добавил символ, затем снял блокировку, тогда другой поток может:

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

  • добавьте другой элемент в список без какой-либо блокировки

  • НЕ выполняйте find() в list ; find может быть предпринята попытка чтения частично обновленной строки

  • НЕ sort() список

  • НЕ erase() элемент из списка

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

Ну, вам нужен некоторый уровень детализации. Наилучшим возможным является блокировка для каждого элемента, но это потенциально очень расточительно для пространства (и потраченная впустую память в конечном итоге влияет на производительность).

Если я не могу заблокировать только один элемент, возникает другой вопрос: если я вызову деструктор для одного из моих объектов в списке, удалит ли он элемент и сделает недействительным его итератор?

Нет. Вызов деструктора обычно не влияет на список (если только сами ваши элементы не знают о списке, содержащем их, и их деструкторы не закодированы для удаления самих себя). Обычно вместо этого вы erase выбираете элемент из списка, который вызывает деструктор и освобождает связанную память кучи. Если элемент сам по себе является необработанным указателем, то вам нужно вызвать delete его самостоятельно (smart_pointer автоматизирует это для вас). Итераторы, указатель, ссылки на удаленный элемент немедленно становятся недействительными.

Ответ №3:

Чтобы управлять списком, вы должны заблокировать весь список. Как только ваш объект извлечен, вы можете снять блокировку списка и заблокировать только вокруг объекта.