Отсюда: Логическая ошибка в моем определенном классе Mutex и способ, которым я использую его в программе производителя-потребителя - pthreads

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

Почему это проблема? Должен ли я передать по значению, а затем написать конструктор копирования?

Какой вред может принести в этом случае отсутствие инкапсуляции? Как мне инкапсулировать что?

Кроме того, Почему передаются ссылки классу Mutex - не лучший дизайн?

Передача ссылки на блокировку - плохая идея: вы не «используете» блокировку, вы только приобретаете, а затем возвращаете ее. Его перемещение затрудняет отслеживание использования вами (критического) ресурса. Передача ссылки на мьютекс-переменную, а не на блокировку, возможно, не так уж и плоха, но все же затрудняет определение того, какие части программы могут быть заблокированы, поэтому этого следует избегать.

Пожалуйста, объясните простым языком с примерами - почему передача ссылок - плохая идея?

9
Aquarius_Girl 1 Янв 2016 в 06:07

2 ответа

Лучший ответ

Я бы разделил это на отдельные вопросы: 1. Когда уместно передавать какой-либо объект по ссылке? 2. Когда уместно делиться мьютексом?

  1. То, как вы передаете объект в качестве аргумента, отражает то, как вы ожидаете разделить время жизни объекта между вызывающим и вызываемым. Если вы передаете по ссылке, вы должны предположить, что либо вызываемый объект будет использовать объект только в течение вызова, либо, если ссылка сохраняется вызываемым объектом, время жизни вызываемого объекта короче, чем указанное. Если вы имеете дело с динамически выделяемыми объектами, вам, вероятно, следует использовать интеллектуальные указатели, которые (среди прочего) позволяют более явно сообщать о ваших намерениях (см. Трактат Херба Саттера по этой теме).

  2. Следует избегать совместного использования мьютекса. Это верно как при передаче по ссылке, так и любым другим способом. Совместное использование мьютекса позволяет объекту подвергаться внутреннему воздействию внешней сущности. Это нарушает базовую инкапсуляцию, и этого достаточно. (См. Любой текст по объектно-ориентированному программированию о достоинствах инкапсуляции). Одним из реальных последствий совместного использования мьютекса является вероятность тупиковой ситуации.

    Вот простой пример:

    • А владеет мьютексом
    • A разделяет мьютекс с B
    • B получает блокировку перед вызовом функции на A
    • Функция A пытается получить блокировку
    • Тупик ..

С точки зрения дизайна, зачем вам делиться мьютексом? Мьютекс защищает ресурс, к которому могут обращаться несколько потоков. Этот мьютекс должен быть скрыт (инкапсулирован) внутри класса, который управляет этим ресурсом. Мьютекс - это лишь один из способов, которым этот класс может защитить ресурс; это деталь реализации, о которой должен знать только класс. Вместо этого поделитесь экземпляром класса, который управляет ресурсом, и позвольте ему обеспечить безопасность потоков внутри себя любым способом, которым он захочет.

5
Ken 2 Июл 2017 в 20:24

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

Кроме того, указав, какой внутренний мьютекс вы используете, вы раскрываете детали реализации ваших потоков, тем самым нарушая абстракцию класса Mutex. Если вы используете pthread_mutex_t, вы, скорее всего, будете использовать потоки ядра (pthreads).

Инкапсуляция также нарушена, потому что ваш Mutex не является единым инкапсулированным объектом, а скорее разбросан по нескольким (возможно, висячим) ссылкам.

Если вы хотите инкапсулировать pthread_mutex_t в класс, сделайте это так

class Mutex {
public:
    void lock(); 
    void unlock();

    // Default and move constructors are good! 
    // You can store a mutex in STL containers with these
    Mutex();
    Mutex(Mutex&&);
    ~Mutex();

    // These can lead to deadlocks!
    Mutex(const Mutex&) = delete;
    Mutex& operator= (const Mutex&) = delete;
    Mutex& operator= (Mutex&&) = delete;

private:
    pthread_mutex_t internal_mutex;
};

Объекты Mutex предназначены для совместного использования в общей области, объявленной в файлах реализации, а не для ее локального объявления и передачи в функциях в качестве ссылки. В идеале вы должны передавать только те аргументы конструктору потока, которые вам нужны. Передача ссылок на объекты, объявленные в области видимости на том же «уровне», что и рассматриваемая функция (в данном случае выполнение потока), обычно приводит к ошибкам в коде. Что произойдет, если область, в которой объявлен мьютекс, больше не существует? Будет ли деструктор mutex аннулировать внутреннюю реализацию мьютекса? Что произойдет, если мьютекс попадает в другой модуль через передачу, и этот модуль запускает свои собственные потоки и думает, что мьютекс никогда не будет блокироваться, это может привести к неприятным взаимоблокировкам.

Также один случай, когда вы хотели бы использовать конструктор перемещения мьютекса, - это, скажем, шаблон фабрики мьютекса, если вы хотите создать новый мьютекс, вы должны выполнить вызов функции, и эта функция вернет мьютекс, который вы затем добавите в свой список мьютексов или передать потоку, который запрашивает его через какие-то общие данные (вышеупомянутый список был бы хорошей идеей для этих общих данных). Однако создание такого шаблона фабрики мьютексов может быть довольно сложным, поскольку вам нужно заблокировать доступ к общему списку мьютексов. Это должно быть весело!

Если намерение автора состояло в том, чтобы избежать глобальной области видимости, тогда объявление ее один раз в файле реализации как статический объект должно быть достаточной абстракцией.

7
idclev 463035818 11 Апр 2018 в 09:26