Назначение через копирование-и-своп против двух блокировок - PullRequest
6 голосов
/ 22 февраля 2011

Заимствование Пример Говарда Хиннанта и изменение его для использования копирования и обмена, это op = thread-safe?

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 Ответ

8 голосов
/ 22 февраля 2011

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

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

Например:

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

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

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

...