оператор присваивания для классов с не копируемым boost::mutex

Я читаю здесь этот старый Часто задаваемые вопросы по теме Boost, где является руководством по реализации конструкции копирования и оператора присваивания для классов, имеющих в качестве члена объект boost::mutexnon-copyable.

Я в порядке с конструктором копирования, но у меня есть некоторые сомнения по поводу оператора присваивания. Действует ли указанная ниже инструкция?

  // old boost thread    
  const counter & operator=( const counter& other ){
     if (this == &other)
        return *this;

     boost::mutex::scoped_lock lock1(&m_mutex < &other.m_mutex ?
                                        m_mutex : other.m_mutex);
     boost::mutex::scoped_lock lock2(&m_mutex > &other.m_mutex ?
                                        m_mutex : other.m_mutex);
     m_value = other.m_value;

     return *this;
}

Не следует ли обновить это до:

 // new boost thread    
 const counter& operator=(const counter& other){
    if (this == &other)
       return *this;

    boost::unique_lock<boost::mutex> l1(m_mutex, boost::defer_lock);
    boost::unique_lock<boost::mutex> l2(other.m_mutex, boost::defer_lock);
    boost::lock(l1,l2);
    m_value = other.m_value;

    return *this;
}

person Abruzzo Forte e Gentile    schedule 25.01.2011    source источник
comment
Я просто хочу отметить, что технически решение, данное в FAQ, не всегда будет работать. Сравнение таких указателей не определено и вполне может дать true или false для обоих сравнений. Правильный способ — использовать специализацию std::less (и kin) либо для void*, либо для boost::mutex*, поскольку функторы функционального сравнения обеспечивают полное упорядочение всех специализаций указателей. (Вам по-прежнему не гарантируется результат в частности, но гарантируется действительный порядок.)   -  person GManNickG    schedule 25.01.2011


Ответы (1)


Прежде всего, я предполагаю, что вопрос заключается в том, чтобы избежать взаимоблокировки при блокировке нескольких произвольных мьютексов. Важно всегда использовать одно и то же соглашение об упорядочении во всем коде, используя набор мьютексов. Если вы можете гарантировать, что мьютекс A всегда будет блокироваться раньше B, B всегда будет блокироваться раньше C, а A всегда раньше C, вы избежите взаимоблокировки.

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

Если вы начинаете с нуля (ранее вы не удерживали в своем коде более одного мьютекса за раз), использование метода Boost определенно предпочтительнее — вам не нужно беспокоиться о точном порядке, пока вы его оставляете. до повышения каждый раз. Если остальная часть вашего кода использует порядок памяти, вы должны использовать его. Если в вашем коде используется совсем другое соглашение, вам нужно применить его и здесь. Ни при каких обстоятельствах вы не должны смешивать соглашения в любом наборе замков, которые могут удерживаться одновременно, это просто напрашивается на неприятности.

Чтобы ответить на вопрос в комментарии:

Пользовательская схема упорядочения блокировок может быть полезна в определенных обстоятельствах, особенно если вам нужно удерживать некоторые блокировки (A) в течение длительного времени, а некоторые (B) — только кратковременно, в то время как удерживать долгую. Например, если вам нужно выполнять длительные задания над объектами типа A, которые кратковременно влияют на множество экземпляров B. По соглашению всегда сначала нужно получить блокировку для A, а затем затем блокировку для B объект:

void doStuff(A& a, std::list<B*> bs)
{
  boost::unique_lock<boost::mutex> la(a.mutex); // lock a throughout
  for (std::list<B*>::iterator ib = bs.begin(); ib != bs.end(); ++ib)
  {
    // lock each B only for one loop iteration
    boost::unique_lock<boost::mutex> lb(ib->mutex);
    // work on a and *ib
    // ...
  }
}

Вы можете отказаться от блокировки A между каждой итерацией цикла и использовать порядок блокировки Boost/C++0x, но в зависимости от того, что делает doStuff(), это может сделать алгоритм более сложным или запутанным. .

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

person pmdj    schedule 25.01.2011
comment
Правильно. Это против тупика. Я не могу понять других соглашений или схем упорядочения, кроме boost::lock или упорядочения памяти. У вас есть пример, на который я могу посмотреть? ПРИМЕЧАНИЕ. Это не только метод boost, но и метод C++0x с использованием std::lock( l1, l2 ). Я согласен с вами.. лучше не вдаваться в подробности. - person Abruzzo Forte e Gentile; 25.01.2011
comment
Пользовательская схема упорядочения блокировок может быть полезна в определенных обстоятельствах, особенно если вам нужно удерживать некоторые блокировки (A) в течение длительного времени, а некоторые (B) только кратковременно, в то время как удерживать более длительное время. Например, если вам нужно выполнять длительные задания над объектами типа A, которые кратковременно влияют на множество экземпляров B. По соглашению всегда сначала нужно получить блокировку для A, затем блокировку для B объект: - person pmdj; 25.01.2011
comment
Пример для пользовательского соглашения о заказе оказался слишком длинным для комментария, поэтому я добавил его в ответ. - person pmdj; 25.01.2011
comment
Интересный пример и объяснение. Большое спасибо за ваше время. Хорошего дня. АФГ - person Abruzzo Forte e Gentile; 25.01.2011