Назначение через copy-and-swap против двух блокировок

займы пример Говарда Хиннанта и изменение его для использования copy-and-swap, является ли это op= потокобезопасным?

struct A {
  A() = default;
  A(A const &x);  // Assume implements correct locking and copying.

  A& operator=(A x) {
    std::lock_guard<std::mutex> lock_data (_mut);
    using std::swap;
    swap(_data, x._data);
    return *this;
  }

private:
  mutable std::mutex _mut;
  std::vector<double> _data;
};

Я считаю, что этот потокобезопасный (помните, что параметр op=передается по значению), и единственная проблема, которую я могу найти, - это тот, который сметен под ковер: копия ctor. Однако это был бы редкий класс, который позволяет копировать-назначение, но не копирование-построение, так что проблема существует одинаково в обеих альтернативах.

учитывая, что самостоятельное назначение настолько редко (по крайней мере, для этого примера), что я не против дополнительной копии, если это произойдет, рассмотрите потенциальную оптимизацию этого != & rhs должен быть либо незначительным, либо пессимизацией. Будет ли какая-либо другая причина предпочесть или избежать ее по сравнению с первоначальной стратегией (ниже)?

A& operator=(A const &rhs) {
  if (this != &rhs) {
    std::unique_lock<std::mutex> lhs_lock(    _mut, std::defer_lock);
    std::unique_lock<std::mutex> rhs_lock(rhs._mut, std::defer_lock);
    std::lock(lhs_lock, rhs_lock);
    _data = rhs._data;
  }
  return *this;
}

кстати, я думаю, что это кратко обрабатывает копию ctor, по крайней мере, для этого класса, даже если это немного тупо:

A(A const &x) : _data {(std::lock_guard<std::mutex>(x._mut), x._data)} {}

1 ответов


Я считаю, что ваше назначение является потокобезопасным (при условии, конечно, никаких ссылок вне класса). Производительность его относительно const A& вариант, вероятно, зависит от A. Я думаю, что для многих A ваша перепись будет такой же быстрой, если не быстрее. Большой встречный пример у меня есть std:: vector (и подобные ему классы).

std:: vector имеет емкость, которая не участвует в его значении. И если lhs имеет достаточную емкость по отношению к rhs, то повторное использование этого емкость, вместо того, чтобы выбрасывать ее в темп, может быть выигрышем производительности.

например:

std::vector<int> v1(5);
std::vector<int> v2(4);
...
v1 = v2;

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

Я отмечаю, что в отношении безопасности потоков оба алгоритма блокируют/открывают два замка. Хотя вариант swap позволяет избежать необходимости запереть их обоих одновременно. Я считаю, что в среднем стоимость блокировки обоих одновременно невелика. Но в ожесточенных случаях использования это может стать проблемой.