Зачем передавать мьютекс в качестве параметра функции, вызываемой потоком?

в некоторых местах я видел, как люди создают пул потоков и создают потоки и выполняют функцию с этими потоками. При вызове этой функции boost:: mutex передается по ссылке. Почему это делается так? Я считаю, что у вас может быть объявлен мьютекс в самой вызываемой функции или может быть объявлен членом класса или глобальным. Кто-нибудь может объяснить?

например

   myclass::processData()
   {
         boost::threadpool::pool pool(2);
         boost::mutex mutex;

         for (int i =0; data<maxData; ++data)
             pool.schedule(boost::bind(&myClass::getData, boost_cref(*this), boost::ref(mutex)));
    }

затем,

    myClass::getData(boost::mutex& mutex)
    {
         boost::scoped_lock(mutex)    // Why can't we have class member variable mutex or                                     
                                      //local mutex here
        //Do somethign Here
}

3 ответов


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

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

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


Я считаю, что у вас может быть объявлен мьютекс в самой вызываемой функции или может быть объявлен членом класса или глобальным. Кто-нибудь может объяснить?

создание нового мьютекса в записи ничего не защищает.

Если вы рассматривали объявление статического (или глобального) мьютекса для защиты нестатических членов, то вы можете также написать программу как однопоточную программу (хорошо, есть некоторые угловые случаи). статическая блокировка блокирует все потоки, кроме одного (предполагая конкурс); это эквивалентно "максимум один поток может работать в теле этого метода за один раз". объявление статического мьютекса для защиты статических данных в порядке. как Дэвид Родригес - дрибеас сформулировал это кратко в комментариях другого ответа:"мьютекс должен быть на уровне данных, которые защищаются".

вы can объявите переменную-член на экземпляр, которая примет обобщенную форму:

class t_object {
public:
    ...
    bool getData(t_data& outData) {
        t_lock_scope lock(this->d_lock);
        ...
        outData.set(someValue);
        return true;
    }

private:
    t_lock d_lock;
};

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

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

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

class t_composition {
public:
    ...
private:
    t_lock d_lock; // << name and data can share this lock
    t_string d_name;
    t_data d_data;
};

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

обе модели (связанные и как переменная-член) могут быть эффективно использованы. что лучше в данном случае зависит от проблемы.


использование локального мьютекса неверно: пул потоков может вызывать несколько экземпляров функций, и они должны работать с одним и тем же мьютексом. Член класса в порядке. Передача мьютекса функции делает ее более универсальной и читаемой. Вызывающий может решить, какой мьютекс передать: член класса или что-нибудь еще.