如何使 std::execution::par_unseq 线程安全?

How to make threads with std::execution::par_unseq thread-safe?

我正在阅读 C++ 并发实战。 它说当你使用 std::execution::par 时,你可以像下面这样为每个内部元素使用互斥锁。

#include <mutex>
#include <vector>

class X {
  mutable std::mutex m;
  int data;

 public:
  X() : data(0) {}
  int get_value() const {
    std::lock_guard guard(m);
    return data;
  }
  void increment() {
    std::lock_guard guard(m);
    ++data;
  }
};

void increment_all(std::vector<X>& v) {
  std::for_each(v.begin(), v.end(), [](X& x) { x.increment(); });
}

但是它说当你使用 std::execution::par_unseq 时,你必须用下面的整个容器互斥体替换这个互斥体

#include <mutex>
#include <vector>

class Y {
  int data;

 public:
  Y() : data(0) {}
  int get_value() const { return data; }
  void increment() { ++data; }
};

class ProtectedY {
  std::mutex m;
  std::vector<Y> v;

 public:
  void lock() { m.lock(); }
  void unlock() { m.unlock(); }

  std::vector<Y>& get_vec() { return v; }
};

void incremental_all(ProtectedY& data) {
  std::lock_guard<ProtectedY> guard(data);
  auto& v = data.get_vec();
  std::for_each(std::execution::par_unseq, v.begin(), v.end(),
                [](Y& y) { y.increment(); });
}

但是即使使用第二个版本,并行算法线程中的y.increament() 也会出现数据竞争条件,因为并行算法线程之间没有锁。

带有 std::execution::par_unseq 的第二个版本如何保证线程安全?

它只是线程安全的,因为您不访问并行算法中的共享数据。

唯一并行执行的是对 y.increment() 的调用。这些可以以任何顺序在任何线程上发生,并且可以任意地相互交错,甚至在单个线程中也是如此。但是 y.increment() 只访问 y 的私有数据,并且每个 y 都与所有其他向量元素不同。所以这里没有数据竞争的机会,因为各个元素之间没有“重叠”。

一个不同的例子是,如果 increment 函数也访问一些在向量的所有不同元素之间共享的全局状态。在这种情况下,现在可能会出现数据竞争,因此需要同步对共享全局状态的访问。但是因为并行无序策略的特殊要求,这里不能只用mutex来同步。

请注意,如果在并行算法的上下文中使用互斥锁,它可能会防止不同的危害:一种用途是使用互斥锁在执行 for-each 的不同线程之间进行同步。这适用于并行执行策略,但不适用于 并行无序。这不是您的示例中的用例,因为在您的情况下没有共享数据,因此我们不需要任何同步。相反,在您的示例中,互斥锁仅将 for-each 的调用与任何 other 线程同步,这些线程可能仍然是 运行 作为较大应用程序的一部分,但没有同步在 for-each 本身中。这是并行和并行无序的有效用例,但在后一种情况下,它不能通过使用每个元素互斥锁来实现。