деструкт залоченного мьютекса
От: niXman Ниоткуда https://github.com/niXman
Дата: 15.11.18 09:26
Оценка:
привет!

есть некоторый класс, использующий boost::recursive_mutex.
при деструкте мьютекса ловлю assert() тут: https://github.com/boostorg/thread/blob/develop/include/boost/thread/pthread/recursive_mutex.hpp#L99
причина в том, что мьютекс залочен, а я всю жизнь знал, что нельзя деструктить залоченный мьютекс.
но проблема еще и в том, что, оказывается, можно деструктить залоченный boost::recursive_mutex на венде.
boost::recursive_mutex на венде реализован как спин-лок, и в деструкте нет никаких проверок на то, залочен мьютекс, или нет. но столь разное поведение создает пролему переносимости кода, потому что вышеописанная ситуация не возникает на венде, и ребята писавшие этот код о проблемем и не знали до линукса.

вопроса у меня два:
1)есть ли мысли, почему бустописатели допустили столь разное поведение?
2)считается ли нормальным, по вашему мнению, деструктить залоченный мьютекс?

спасибо.
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re: деструкт залоченного мьютекса
От: Maniacal Россия  
Дата: 15.11.18 09:39
Оценка:
Здравствуйте, niXman, Вы писали:

X>2)считается ли нормальным, по вашему мнению, деструктить залоченный мьютекс?


Mutex objects are used to protect shared data from being concurrently accessed. If a mutex object is destroyed while a thread is blocked waiting for the lock, critical sections and shared data are no longer protected.

The C++ Standard, [thread.mutex.class], paragraph 5 [ISO/IEC 14882-2014], states the following:

The behavior of a program is undefined if it destroys a mutex object owned by any thread or a thread terminates while owning a mutex object.

Similar wording exists for std::recursive_mutex, std::timed_mutex, std::recursive_timed_mutex, and std::shared_timed_mutex. These statements imply that destroying a mutex object while a thread is waiting on it is undefined behavior.

Отсюда.
Re[2]: деструкт залоченного мьютекса
От: niXman Ниоткуда https://github.com/niXman
Дата: 15.11.18 09:42
Оценка:
Здравствуйте, Maniacal, Вы писали:

я знаю что написано в стандарте(но бустописатели, похоже нет), спасибо!

мой вопрос был про "по вашему мнению".
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[3]: деструкт залоченного мьютекса
От: Maniacal Россия  
Дата: 15.11.18 09:48
Оценка:
Здравствуйте, niXman, Вы писали:

X>Здравствуйте, Maniacal, Вы писали:


X>я знаю что написано в стандарте(но бустописатели, похоже нет), спасибо!


X>мой вопрос был про "по вашему мнению".


По моему мнению, и буст подтверждает его, всё зависит от реализации мьютекса. В разных реализациях разный результат, единого стандарта нет (поведение не предусмотрено стандартом). В винде закрыть HANDLE залоченного мьютекса вообще в порядке вещей. Он просто разлачивается. IMHO, в Windows специально реализован такой механизм, чтобы закрытие процесса максимально просто приводило автоматически к освобождению всех ресурсов.
Re[4]: деструкт залоченного мьютекса
От: niXman Ниоткуда https://github.com/niXman
Дата: 15.11.18 09:51
Оценка:
Здравствуйте, Maniacal, Вы писали:

M>По моему мнению, и буст подтверждает его, всё зависит от реализации мьютекса. В разных реализациях разный результат, единого стандарта нет (поведение не предусмотрено стандартом). В винде закрыть HANDLE залоченного мьютекса вообще в порядке вещей. Он просто разлачивается. IMHO, в Windows специально реализован такой механизм, чтобы закрытие процесса максимально просто приводило автоматически к освобождению всех ресурсов.


это понятно.
не понятно, как можно доверять коду, который лочит мьютекс большее колво раз, чем разлочивает? и такое поведение мьютекса на венде даже стимулирует не задумываться об этом...
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[5]: деструкт залоченного мьютекса
От: niXman Ниоткуда https://github.com/niXman
Дата: 15.11.18 09:58
Оценка:
Здравствуйте, niXman, Вы писали:


X>не понятно, как можно доверять коду, который лочит мьютекс большее колво раз, чем разлочивает? и такое поведение мьютекса на венде даже стимулирует не задумываться об этом...


мне кажется, это сродни утверждению что двойной free()/close() — тоже нормально, ведь при повторном вызове ничего еще раз освобождено/закрыто не будет =)
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re: деструкт залоченного мьютекса
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 15.11.18 10:37
Оценка: +2
Здравствуйте, niXman, Вы писали:

X>2)считается ли нормальным, по вашему мнению, деструктить залоченный мьютекс?


По моему мнению — это не нормально.

Все равно что грохать объект с необнуленным счетчиком ссылок.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re: деструкт залоченного мьютекса
От: Videoman Россия https://hts.tv/
Дата: 15.11.18 10:46
Оценка: +1
Здравствуйте, niXman, Вы писали:

X>1)есть ли мысли, почему бустописатели допустили столь разное поведение?

Потому-что контракт примитива не нарушен. Если абстракция не разрешает разрушение объекта, то разработчики не обязаны подкладывать (иногда не бесплатные) соломки. Любая реализация, такого плана библиотек, стремится быть как можно более эффективной и использовать встроенные средства системы на которой работает. Максимум, можно предусмотреть assert в режиме отладки.

X>2)считается ли нормальным, по вашему мнению, деструктить залоченный мьютекс?

Нет. В подавляющем числе случаев это является ошибкой, т.к. ресурсы, вынуждено, либо остаются без защиты, либо разрушаются вместе с объектом их защищающим. Я бы очень сильно напрягся, если бы встретил такое у меня в коде.
Отредактировано 15.11.2018 10:47 Videoman . Предыдущая версия .
Re: деструкт залоченного мьютекса
От: B0FEE664  
Дата: 15.11.18 10:49
Оценка:
Здравствуйте, niXman, Вы писали:

X>2)считается ли нормальным, по вашему мнению, деструктить залоченный мьютекс?

При выходе из программы это может быть оправдано.
И каждый день — без права на ошибку...
Re[5]: деструкт залоченного мьютекса
От: Maniacal Россия  
Дата: 15.11.18 10:49
Оценка:
Здравствуйте, niXman, Вы писали:

X>это понятно.

X>не понятно, как можно доверять коду, который лочит мьютекс большее колво раз, чем разлочивает? и такое поведение мьютекса на венде даже стимулирует не задумываться об этом...

Рекурсивный мьютекс есть смысл использовать разве что как критическую секцию, а не для защиты данных напрямую. При ошибке вместе с раскруткой стек и мьютекс разлочится столько раз, сколько залочился.
Re[2]: деструкт залоченного мьютекса
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 15.11.18 11:07
Оценка: +4 :)
Здравствуйте, B0FEE664, Вы писали:

X>>2)считается ли нормальным, по вашему мнению, деструктить залоченный мьютекс?

BFE>При выходе из программы это может быть оправдано.

Ага, есть мнение что и память можно не освобождать.

Потом такую программу преобразуют в DLL и все кругом радуются.
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
Re[3]: деструкт залоченного мьютекса
От: B0FEE664  
Дата: 15.11.18 12:31
Оценка: -1
Здравствуйте, Коваленко Дмитрий, Вы писали:

КД>Потом такую программу преобразуют в DLL и все кругом радуются.


Ещё одна причина не использовать DLL.
И каждый день — без права на ошибку...
Re[5]: деструкт залоченного мьютекса
От: Кодт Россия  
Дата: 20.11.18 02:00
Оценка: 16 (2) +1
Здравствуйте, niXman, Вы писали:

X>это понятно.

X>не понятно, как можно доверять коду, который лочит мьютекс большее колво раз, чем разлочивает? и такое поведение мьютекса на венде даже стимулирует не задумываться об этом...

Скорее, "не понятно, как можно доверять коду, который сам из-под себя вышибает табуретку".

Мьютекс как объект сам является ресурсом.
Вопрос "кто сторожит сторожей" отдан на откуп погромисту.

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

Пример, как можно самого себя расстрелять:
1. в главном потоке создаются данные и защищающий их мьютекс
2. запускается рабочий поток (не являющийся агрегатом владельца этих данных! это важный момент!)
3. и главный, и рабочий поток конкурируют за мьютекс...
4. главный поток удаляет данные-и-мьютекс — при том, что рабочий поток ещё не остановлен
5. рабочий поток, на выбор
5.а) успел захватить мьютекс, — получим ассерт в деструкторе и, опционально, стрельбу по убиваемым данным
5.б) не успел, — получим стрельбу по памяти в убитом мьютексе при его захвате

Если бы мы наловчились даже не ассертить, а захватывать мьютекс в деструкторе, — просто форсировали бы пункт 5.б
К реализации мьютекса, как понимаешь, это никакого отношения не имеет. Вопрос лишь в том, какой минимальный погром мы получим при нарушении.
— если мьютекс спрятан за хэндл операционной системы — то, либо она покрутит пальцем у виска, либо жахнет исключением защиты.
— если это объект пользовательского пространства (инкапсулирующий хэндлы-шмэндлы), — то все прелести расстрела памяти.

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

Первое — с помощью shared_ptr и т.п.
Второе — join потока в деструкторе (или иной терминальной точке) владельца до деструктора мьютекса.

(shared_ptr обеспечивает атомарность счётчика. Так что, если уж пришла пора данным в морг, — значит, в морг, и никто не успел просунуть свои шаловливые ручки с целью поработать с грядущим трупом. Поэтому защищать деструктор данных мьютексом не нужно. Вот если есть несколько желающих поменять/прочитать сам указатель — тогда да, доступ к указателю надо защищать. Ну, это азы).
Перекуём баги на фичи!
Re[6]: деструкт залоченного мьютекса
От: niXman Ниоткуда https://github.com/niXman
Дата: 25.11.18 12:22
Оценка:
Здравствуйте, Кодт, Вы писали:

К>Скорее, "не понятно, как можно доверять коду, который сам из-под себя вышибает табуретку".


т.е., двойной free() или использование висячего указателя но при этом не вышибая табуретку — тоже норм?
пачка бумаги А4 стОит 2000 р, в ней 500 листов. получается, лист обычной бумаги стОит дороже имперского рубля =)
Re[7]: деструкт залоченного мьютекса
От: Кодт Россия  
Дата: 25.11.18 22:40
Оценка:
Здравствуйте, niXman, Вы писали:

К>>Скорее, "не понятно, как можно доверять коду, который сам из-под себя вышибает табуретку".

X>т.е., двойной free() или использование висячего указателя но при этом не вышибая табуретку — тоже норм?

Венда тебя любит, а ты её бесишь!

Ненормально. Но не все вещи легко и бесплатно диагностируются.
Двойной free() подвержен проблеме АБА. Кто-то мог успеть выделить память ровно в том же месте.
Хэндл мьютекса тоже мог быть переиспользован.
Перекуём баги на фичи!
Re[6]: деструкт залоченного мьютекса
От: GarryIV  
Дата: 27.11.18 19:43
Оценка:
Здравствуйте, niXman, Вы писали:

X>мне кажется, это сродни утверждению что двойной free()/close() — тоже нормально, ведь при повторном вызове ничего еще раз освобождено/закрыто не будет =)


У людей из .Net и Java нормально

If an object's Dispose method is called more than once, the object must ignore all calls after the first one. The object must not throw an exception if its Dispose method is called multiple times.



Closes this stream and releases any system resources associated with it. If the stream is already closed then invoking this method has no effect.

WBR, Igor Evgrafov
Re[7]: деструкт залоченного мьютекса
От: Коваленко Дмитрий Россия http://www.ibprovider.com
Дата: 28.11.18 06:11
Оценка:
Здравствуйте, GarryIV, Вы писали:

GIV>Здравствуйте, niXman, Вы писали:


X>>мне кажется, это сродни утверждению что двойной free()/close() — тоже нормально, ведь при повторном вызове ничего еще раз освобождено/закрыто не будет =)


GIV>У людей из .Net и Java нормально

GIV>

GIV>If an object's Dispose method is called more than once, the object must ignore all calls after the first one. The object must not throw an exception if its Dispose method is called multiple times.



GIV>

GIV>Closes this stream and releases any system resources associated with it. If the stream is already closed then invoking this method has no effect.


И есть еще одно связанное правило — после Dispose любой другой метод (включая Close) должен кидать исключение ObjectWasDisposed(?).
-- Пользователи не приняли программу. Всех пришлось уничтожить. --
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.