Объясните, пожалуйста
1. почему нельзя два раза вызывать std::mutex::lock() (т.е. не вызвав перед вторым вызовом unlock())?
2. почему это UB, а не исключение, например?
В Win API поток может заходить повторно в метод защищенный WinAPI-шным mutex-ом, если я не ошибаюсь.
Здравствуйте, Максим Рогожин, Вы писали:
МР>1. почему нельзя два раза вызывать std::mutex::lock() (т.е. не вызвав перед вторым вызовом unlock())?
Для рекурсивного мьютекса можно вызывать без UB.
Здравствуйте, Kernan, Вы писали:
K>Для рекурсивного мьютекса можно вызывать без UB.
А зачем отдельный рекурсивный мьютекс? Почему std::mutex не сделали рекурсивным?
И почему называется std::recursive_mutex, а не std::reentrant_mutex?
K>>Для рекурсивного мьютекса можно вызывать без UB. МР>А зачем отдельный рекурсивный мьютекс? Почему std::mutex не сделали рекурсивным?
1) Реализация нерекурсивного мутекса может быть легковеснее рекурсивного
2) Зачастую (а точнее почти всегда) логика приложения не предполагает рекурсивного входа в мутекс. В таком случае своеобразный ассерт на рекурсию может быть признаком того что девелопер пытается поломать задуманную архитектуру. Впрочем UB — это так себе ассерт
МР>И почему называется std::recursive_mutex, а не std::reentrant_mutex? Реентерабельность
Как много веселых ребят, и все делают велосипед...
Здравствуйте, Максим Рогожин, Вы писали:
МР>Здравствуйте, Kernan, Вы писали:
K>>Для рекурсивного мьютекса можно вызывать без UB. МР>А зачем отдельный рекурсивный мьютекс? Почему std::mutex не сделали рекурсивным? МР>И почему называется std::recursive_mutex, а не std::reentrant_mutex?
Термин reentrant — это обычно для многопоточных сценариев применяется.
Для мьютекса получается именно рекурсия: залочил, снова залочил. Тогда надо дважды сделать разлочку.
Здравствуйте, ononim, Вы писали:
O>2) Зачастую (а точнее почти всегда) логика приложения не предполагает рекурсивного входа в мутекс. В таком случае своеобразный ассерт на рекурсию может быть признаком того что девелопер пытается поломать задуманную архитектуру. Впрочем UB — это так себе ассерт
Как так? Вот сделали мы класс
class SomeClass {
Resource resource;
std::mutex m_mutex;
public:
void doSomething1() {
std::lock_guard<mutex> locker(m_mutex);
// modify resource
}
void doSomething2() {
std::lock_guard<mutex> locker(m_mutex);
// modify resourse
doSomething1(); // вот и рекурсивный вход в мьютекс
}
};
Такая ситуация наоборот часто распространена, мне кажется?
Здравствуйте, Mr.Delphist, Вы писали:
MD>Термин reentrant — это обычно для многопоточных сценариев применяется.
А std::mutex может для чего-то в однопоточном сценарии использоваться что-ли...???
Здравствуйте, Максим Рогожин, Вы писали:
МР>Такая ситуация наоборот часто распространена, мне кажется?
Такое бывает, у меня была задача где мне понадобился рекурсивный мьютекс, хотя я, честно говоря, поленился рефакторить код чтобы этого избежать. ИМХО, такой интерфейс как-то неверно сделан т.к. операция doSomething2 должна делать одно и только одно изменение над объектом, а тут, кмк, нарушение SRP.
В твоём случае выполнения конкретных операций можно сделать в приватных методах не защищённых мьютексом, а в интерфейсных методах делать мьютекс и защищать одну или цепочку операций.
Здравствуйте, Максим Рогожин, Вы писали:
МР>Здравствуйте, Kernan, Вы писали:
K>>Для рекурсивного мьютекса можно вызывать без UB. МР>А зачем отдельный рекурсивный мьютекс? Почему std::mutex не сделали рекурсивным? МР>И почему называется std::recursive_mutex, а не std::reentrant_mutex?
Ты бы прочитал для начала книгу которую я тебе рекомендовал в другом треде.
Здравствуйте, Kernan, Вы писали:
K>Ты бы прочитал для начала книгу которую я тебе рекомендовал в другом треде.
Прочитаю, спасибо. Мне тут просто по работе немного многопоточности добавить надо.
Здравствуйте, Максим Рогожин, Вы писали:
МР>Здравствуйте, Kernan, Вы писали:
K>>Ты бы прочитал для начала книгу которую я тебе рекомендовал в другом треде. МР>Прочитаю, спасибо. Мне тут просто по работе немного многопоточности добавить надо.
Так не проще ли задать конкретый вопрос как лучше реализовать решения для того или иного случая? Созадать пару потоков это не проблема.
Здравствуйте, Kernan, Вы писали:
K>Так не проще ли задать конкретый вопрос как лучше реализовать решения для того или иного случая? Созадать пару потоков это не проблема.
Да, я уже сделал что мне надо было. Просто хочется узнать почему два раза позвать lock() это UB. Что-то не улавливаю пока...
Например, будет кто-то другой мой код редактировать и позовет метод из метода (как в примере, который я привел) и что? UB? Как отлавливать такие ситуации? Все методы класса просматривать и разбираться может или нет получится два вызова lock()?
Здравствуйте, Максим Рогожин, Вы писали:
K>>Так не проще ли задать конкретый вопрос как лучше реализовать решения для того или иного случая? Созадать пару потоков это не проблема. МР>Да, я уже сделал что мне надо было. Просто хочется узнать почему два раза позвать lock() это UB. Что-то не улавливаю пока...
Теперь и в WinAPI есть нерекурсивный примитив, это Slim Reader/Writer (SRW) Lock
Отсутствие поддержки рекурсии в нём позволило его сделать действительно slim.
std::mutex в студии работают именно на нём.
МР>Например, будет кто-то другой мой код редактировать и позовет метод из метода (как в примере, который я привел) и что? UB? Как отлавливать такие ситуации? Все методы класса просматривать и разбираться может или нет получится два вызова lock()?
Вообще, желательно контролировать все вхождения в мьютексы.
И контролировать что вызывается из-под мьютекса (стараясь не вызвать из своих мьютексов пользовательские коллбэки).
Хаотичные правки здесь ни к чему хорошему не приведут.
Потому что если хаос из одного мьютекса приведёт к необходимости рекурсии, то на двух мьютексах это выльется в deadlock.
Ну а когда всё под контролем, рекурсивность и не нужна вовсе.
Ну и хорошая реализация в отладочной сборке, скорее всего, будет ругаться на повторный захват нерекурсивного мьютекса.
Здравствуйте, Максим Рогожин, Вы писали:
K>>Так не проще ли задать конкретый вопрос как лучше реализовать решения для того или иного случая? Созадать пару потоков это не проблема. МР>Да, я уже сделал что мне надо было. Просто хочется узнать почему два раза позвать lock() это UB. Что-то не улавливаю пока...
Потому что в C++ ты не платишь за то, что не используешь.
Обычный мьютекс — это просто битик. Если тебе удается его атомарно сменить с 0 на 1, то ты его захватил, если нет, то приходится висеть.
Рекурсивный мьютекс сложнее. Там если не захватил, то надо проверить, какой поток им владеет, и если это текущий поток, то увеличить счетчик захватов. В общем, сразу появляются всякие проверки, счетчики, системные вызовы/TLS...
МР>Например, будет кто-то другой мой код редактировать и позовет метод из метода (как в примере, который я привел) и что? UB? Как отлавливать такие ситуации? Все методы класса просматривать и разбираться может или нет получится два вызова lock()?
Если боишься, просто воткни recursive_mutex вместо mutex. Никто же не запрещает... Или сделай четкое разделение на locked/unlocked методы.
Здравствуйте, andrey.desman, Вы писали:
AD>Обычный мьютекс — это просто битик. Если тебе удается его атомарно сменить с 0 на 1, то ты его захватил, если нет, то приходится висеть.
Ты путаешь mutex и spinlock. Мьютекс может при захвате перейти в режим ядра для ожидания.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Здравствуйте, Максим Рогожин, Вы писали:
МР>1. почему нельзя два раза вызывать std::mutex::lock() (т.е. не вызвав перед вторым вызовом unlock())? МР>2. почему это UB, а не исключение, например?
Из экономии.
Если std::mutex'у позволено рассчитывать на то, что с ним будут обращаться правильно, его реализация может быть немного более эффективной.
МР>В Win API поток может заходить повторно в метод защищенный WinAPI-шным mutex-ом, если я не ошибаюсь.
Здравствуйте, Максим Рогожин, Вы писали:
МР>И почему называется std::recursive_mutex, а не std::reentrant_mutex?
Потому что объект с такой семантикой получил название рекурсивного мутекса задолго до того, как его внесли в библиотеку C++. И было бы глупо изобретать новое название взамен общепринятого.
Здравствуйте, lpd, Вы писали:
AD>>Обычный мьютекс — это просто битик. Если тебе удается его атомарно сменить с 0 на 1, то ты его захватил, если нет, то приходится висеть. lpd>Ты путаешь mutex и spinlock. Мьютекс может при захвате перейти в режим ядра для ожидания.
Я ничего не путаю. Может перейти, а может и не перейти. Зачем переходить, если можно не?
Re[10]: Почему повторный вызов mutex::lock это UB?
Здравствуйте, andrey.desman, Вы писали:
lpd>>Ты путаешь mutex и spinlock. Мьютекс может при захвате перейти в режим ядра для ожидания.
AD>Я ничего не путаю. Может перейти, а может и не перейти. Зачем переходить, если можно не?
Это да, может и не перейти. Однако если перейдет, все равно в ядре будет список ждущих потоков. Останется только пройти по нему и поискать себя. Уж не знаю, экономили ли авторы stl на этих затратах, или просто выбрали такое поведение без рекурсивного лока как более простое.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)