View in Telegram
std::lock #опытным Сейчас уже более менее опытные разрабы знают про std::scoped_lock и как он решает проблему блокировки множества мьютексов. Однако и в более старом стандарте С++11 есть средство, позволяющее решать ту же самую проблему. Более того std::scoped_lock - это всего лишь более удобная обертка над этим средством. Итак, std::lock. Эта функция блокирует 2 и больше объектов, чьи типы удовлетворяют требованию Locable. То есть для них определены методы lock(), try_lock() и unlock() с соответствующей семантикой. Причем порядок, в котором блокируются объекты - не определен. В стандарте сказано, что объекты блокируются с помощью неопределенной серии вызовов методов lock(), try_lock и unlock(). Однако гарантируется, что эта серия вызовов не может привести к дедлоку. Собстна, для этого все и затевалось. Штука эта полезная, но не очень удобная. Сами посудите. Эта функция просто блокирует объекты, но не отпускает их. И это в эпоху RAII. Ай-ай-ай. Поэтому ее безопасное использование выглядит несколько вычурно:
struct SomeSharedResource {
  void swap(SomeSharedResource& obj) {
    {
      // !!!
          std::unique_lock<std::mutex> lk_c1(mtx, std::defer_lock);
          std::unique_lock<std::mutex> lk_c2(obj.mtx, std::defer_lock);
          std::lock(mtx, obj.mtx);
      // handle swap
    }
  }
  std::mutex mtx;
};

int main() {
  SomeSharedResource resource1;
  SomeSharedResource resource2;  
  std::mutex m2;
  std::thread t1([&resource1, &resource2] {
    resource1.swap(resource2);
    std::cout << "1 Do some work" << std::endl;
  });
  std::thread t2([&resource1, &resource2] {
    resource2.swap(resource1);
    std::cout << "2 Do some work" << std::endl;
  });

  t1.join();  
  t2.join();
}
Раз мы все-таки за безопасность и полезные практики, то нам приходится использовать std::unique_lock'и на мьютексах. Только нужно передать туда параметр std::defer_lock, который говорит, что не нужно локать замки при создании unique_lock'а, его залочит кто-то другой. Тем самым мы убиваем 2-х зайцев: и RAII используем для автоматического освобождения мьютексов, и перекладываем ответственность за блокировку замков на std::lock. Можно использовать и более простую обертку, типа std::lock_guard:
struct SomeSharedResource {
  void swap(SomeSharedResource& obj) {
    {
      // !!!
          std::lock(mtx, obj.mtx);
          std::lock_guard<std::mutex> lk_c1(mtx, std::adopt_lock);
          std::lock_guard<std::mutex> lk_c2(obj.mtx, std::adopt_lock);
      // handle swap
    }
  }
  std::mutex mtx;
};
Здесь мы тоже используем непопулярный конструктор std::lock_guard: передаем в него параметр std::adopt_lock, который говорит о том, что мьютекс уже захвачен и его не нужно локать в конструкторе lock_guard. Можно и ручками вызвать .unlock() у каждого замка, но это не по-православному. Использование unique_lock может быть оправдано соседством с условной переменной, но если вам доступен C++17, то естественно лучше использовать std::scoped_lock. Use modern things. Stay cool. #cpp11 #cpp17 #concurrency
Love Center - Dating, Friends & Matches, NY, LA, Dubai, Global
Love Center - Dating, Friends & Matches, NY, LA, Dubai, Global
Find friends or serious relationships easily