Pull to refresh

Comments 14

Статья в целом понравилась, но есть несколько моментов, на которых, на мой взгляд, стоит заострять внимание и не использовать их в примерах. Не хватает некоторой глубины объяснения, для чего нужны те или иные фичи.
Например, не объяснено, для чего нужны scoped lock'и. Более того, большая часть примеров их не использует. А нужны они для написания exception safe кода. Если исключение будет кинуто после lock, но до unlock, то мьютекс останется захваченным и, через некоторое время программа зависьнет. Использование scoped lock'ов позволяет избежать таких ситуаций и всегда автоматически разблокировать мьютекс при выходе из критической секции.

Про пример с перебрасыванием исключение в другом треде, хотелось бы узнать, кто и когда освобождает память, алоцированную под это исключение. Я могу ошибаться, но разве указатель останется валидным после того, как исключение будет обработано?

Про recursive_mutex стоит сказать, что его надо расценивать как некий хак. Первым делом надо искать возможность обойтись обычным мьютексом.

Про mutable стоит упомянуть смысл этого модификатора. Иногда метод ведет себя как константный, снаружи изменение объекта незаметно, семантика у метода не модифицирующая. Но по факту что-то меняется. Это может быть мьютекс, как в это примере, а может быть кеш. В таком случае можно метод сделать const, а изменяемые им переменные mutable. Если метод при этом стал не thread safe, то надо это написать в омментарии в заголовочном файле.
Спасибо за конструктивные дополнения!
По поводу примера с перебрасыванием исключений: нет, указатель валидным не останется, поэтому в контейнер помещаются копии исключений (используя std::current_exception), а не указатели на них.
Действительно. Собственно такие детали очень важны для понимания материала.
Да, как-то статья не для новичков вышла, а для тех, кто переходит с С++ 2003 на С++11. Но тогда можно кое-что и повыкидывать из статьи. Я понимаю ваши сложности — вы сами с опытом и переходили с С++ 2003, работали с потоками 100500 лет, но попытались написать статью для новичков. Но мне в целом понравилось, видимо бэкграунд у нас схожий.
Поддерживаю, особенно о мелочах, которые следует уточнять. Например, в начале статьи не совсем понятно, в какой момент происходит запуск нового потока, но очень акцентируется внимание на join. В разделе о блокировках я бы, в первую очередь, расписал о проблемах с конкуренцией потоков в работе с данными и чем отличаются мутексы от простых переменных.

Возможно, раз статья затрагивает с++, нужно упомянуть о других библиотеках, например старину pthread.
Вообще очень странное, мягко говоря, решение ставить блокировку внутри цикла for в функции addrange.
Блокировка мьютексами — это достаточно дорогая операция и лучше бы ее вынести из цикла.
При выполнении этой программы произойдет deadlock (взаимоблокировка, т.е. заблокированный поток так и останется ждать). Причиной является то, что контейнер пытается получить мьютекс несколько раз до его освобождения

По моему вы выбрали не самый лучший пример для deadlock. Это проблема проектирования классов, и есть весьма определенные приемы как не встать на эти грабли.
Да и к тому же такую проблему с лета увидеть вообще сложно (собстно поэтому она и появляется), мне потребовалось пару минут что бы вчухать где же там deadlock.
Аналогично. Причем, не знаю как реализовано в c++11, но в бусте, например, для платформы Win в качестве обычного нерекурсивного mutex все равно используется критическая секция, которая сама по себе поддерживает рекурсию и, следовательно, никакого дедлока в данном примере и в помине не будет, что немного сбивает с толку.
А зачем вообще в container::addrange() блокировки?
Если по хорошему, то в addrange нужна общая блокировка над циклом for, но при этом не стоит вызывать внутри функцию add() содержащую блокировки. Лучше напрямую в addrange() использовать push_back(). Ну и еще совсем было бы хорошо, раз уж используется добавление в контейнер STL, добавлять диапазон не одиночными значениями, а используя итераторы — тогда в функцию addrange() достаточно было бы передать всего два параметра begin_iterator и end_iterator для добавляемого диапазона и вообще избавиться от этого внешнего цикла, но это уже не имеет к теме поста никакого отношения конечно.
Здесь и выходит на сцену std::recursive_mutex, который позволяет получать тот же мьютекс несколько раз. Максимальное количество получения мьютекса не определено, но если это количество будет достигно, то lock бросит исключение std::system_error.


Не понял, как то, что не определено, может быть достигнуто?
Неправильно выразился: «Максимальное кол-во мьютексов заранее не известно»
Возможно, конечно, с опозданием, но все-таки лучше помечать статью как Перевод, если вы делаете перевод, или хотя бы сделать ссылку на оригинальную статью (http://www.codeproject.com/Articles/598695/Cplusplus11-threads-locks-and-condition-variables), иначе как-то некрасиво получается.
В C++17 будет shared_mutex. Это то же самое что rwlock. Его свойства вполне известны — он позволяет захватываться в режиме shared и exclusive. Если захвачен в режиме shared, то при попытке захвата в режиме exclusive будет блокировка потока, однако остальные захваты в режиме shared не приводят к блокировке. Если захвачен в режиме exclusive, то любые последующие попытки захвата в любых режимах приведут к блокировке.
Существует множество алгоритмов (см. https://ru.wikipedia.org/wiki/%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D1%87%D0%B8%D1%82%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%D1%85-%D0%BF%D0%B8%D1%81%D0%B0%D1%82%D0%B5%D0%BB%D1%8F%D1%85)
В Windows (копать InitializeSRWLock()) и Linux (см pthread_rwlock) поддерживается нативно ОСью

Надеюсь кому нибудь будет полезно и познавательно.

Однако меня интересует следующий объект синхронизации:
— два метода lock( int index) и unlock( int index )
— если сначала был вызван метод lock( 1 ), а затем lock( 2) то есть если методы lock() вызываются с разными аргументами, то блокировки не происходит. Если аргументы совпадают, то последний вызвавший поток блокируется.
То есть блокировка происходит по индексу. Объект синхронизации хранит в себе индексы. Хранит в себе столько индексов, сколько потоков юзают этот Объект синхронизации.
— метод unlock() освобождает индекс.
— Произвольное количество потоков

Интересует как такой объект синхронизации называется, какие у него есть известные реализации, ссылки, статьи и т.п.
Sign up to leave a comment.

Articles