【发布时间】:2022-01-13 14:58:20
【问题描述】:
这是在考虑Thread sanitizer warnings after using parallel std::for_each时想到的。
具有并行执行策略的std::for_each 等算法可以在实现创建的工作线程中执行代码。这些线程是否与调用线程对for_each 的调用和返回同步,或者类似的东西?常识似乎表明他们应该这样做,但我在 C++20 标准中找不到保证。
考虑以下简单示例 (try on godbolt):
#include <algorithm>
#include <execution>
#include <iostream>
void increment(int &a) {
a++;
}
int main(void) {
constexpr size_t n = 1000;
static int arr[n];
arr[0] = 3;
std::for_each(std::execution::par, arr, arr+n, increment);
std::cout << arr[0] << std::endl;
return 0;
}
这旨在始终输出4。
实现可能会在另一个线程中调用increment(arr[0]),该线程执行arr[0]++。在intro.races p10 的意义上,主线程中的arr[0] = 3 是否发生在之前 arr[0]++?同样,arr[0]++ 发生在arr[0] 在std::cout << arr[0] 中的负载之前吗?我天真地期望他们应该这样做,但我看不出有任何方法可以证明这一点。 algorithm.parallel 似乎没有包含与周围代码同步的任何内容。
如果不是,则该示例包含数据竞争并且其行为未定义。这会使正确使用std::execution::par 变得相当困难,我想知道这是否是一个缺陷。
如果没有这样的保证,实现可能会执行以下操作:
std::atomic<int *> work = nullptr;
void do_work() {
int *p;
while (!(p = work.load(std::memory_order_relaxed)))
std::this_thread::yield();
(*p)++;
}
// started at program startup
std::thread worker_thread(do_work);
int main() {
// ...
arr[0] = 3;
// for_each does the following:
work.store(&arr[0], std::memory_order_relaxed);
worker_thread.join();
// ...
}
如果是这样,那么我们真的会进行数据竞赛。
【问题讨论】:
-
我认为this 可能是您正在寻找的保证
-
@NathanOliver:如果我们将“X 完成时的阻塞”解释为 X 的效果发生在解除阻塞之前,那可能会使用以下代码来处理比赛,并且在线程解除阻塞时可见。该标准实际上并没有在任何地方说,这是一个单独的问题,请参阅stackoverflow.com/questions/70228390/…
标签: c++ concurrency language-lawyer c++20