【问题标题】:How to work around std::get<>()'s lack of concurency specification如何解决 std::get<>() 缺乏并发规范的问题
【发布时间】:2019-06-07 16:05:28
【问题描述】:

以下代码块:

  1. 技术上无效,因为std::get&lt;&gt;() 不是线程安全的。参考:Is using `std::get<I>` on a `std::tuple` guaranteed to be thread-safe for different values of `I`?

  2. 据我所知,现在和可预见的未来,在所有 std::tuple&lt;&gt; 的实施中都是有效的。

    #include <tuple>
    #include <atomic>
    #include <thread>

    // Out of my control
    using data_t = std::tuple<int, int, int, int>;
    void foo(data_t); 
    //

    int main() {
        data_t landing;
        std::atomic<int> completed = 0;

        // Whichever thread pings last will be the one performing foo()
        auto ping = [&](){
            if(++completed == 4) {
                foo(landing);
            }
        };

        std::thread a([&](){ std::get<0>(landing) = 1; ping(); });
        std::thread b([&](){ std::get<1>(landing) = 2; ping(); });
        std::thread c([&](){ std::get<2>(landing) = 3; ping(); });
        std::thread d([&](){ std::get<3>(landing) = 4; ping(); });

        a.join();
        b.join();
        c.join();
        d.join();

        return 0;
    }

为了让事情变得更有趣,实际的代码充满了可变参数模板,因此编写一个一次性着陆台结构来处理这种情况并不能解决问题。它必须是一个通用的解决方案。

我目前的选择是:

  • 使用改写后的 std::get&lt;&gt; 文档有效地重新实现 std::tuple&lt;&gt;,这既浪费时间又浪费代码。
  • 推动std::get&lt;&gt;(std::tuple) 的提案以提供类似于std::vector&lt;&gt; 的保证,并记录代码仅在尚未发布的标准版本中有效的事实。
  • 忽略这个问题,相信事实上,这几乎肯定总是有效的。

这些在短期内都不是特别好...所以我的问题是:

  • 我是否遗漏了使第 2 点无效的内容?
  • 是否有更好的解决方法可以让实现在技术上有效,而不必支持过多的额外代码。
  • 欢迎对此主题提出任何其他意见。

【问题讨论】:

  • 为什么一切都很糟糕?在我看来,第 2 点是一条可行的路——推送提案,一旦接受,就使用。附带说明一下,我看不出这个问题对已经提到的问题有什么补充。
  • @SergeyA 因为避免使用 UB 通常是个好主意?
  • 向标准委员会提交您的第一份提案的绝佳机会!
  • @Frank 在答案中表达的相同概念刚刚获得了 5 票赞成,因此您可能会重新考虑这是一个糟糕的主意。
  • “并记录代码仅在尚未发布的标准版本中有效这一事实” - 您还可以考虑针对标准提出 defect图书馆。一些缺陷具有追溯应用的解决方案(例如 IIRC LWG2101)。

标签: c++ multithreading tuples language-lawyer c++17


【解决方案1】:

推动std::get&lt;&gt;(std::tuple) 的提案以提供类似于std::vector&lt;&gt; 的保证,并记录代码仅在该标准尚未发布的版本中有效的事实。

我认为这是要走的路,因为它为整个 C++ 社区提供了价值,不应该成为实施者的负担。这也是write your first proposal的绝佳机会。

我建议这样做,现在假设这会起作用,即使它是 UB。如果您的软件是超级关键的(例如飞行控制系统),并且您希望 100% 确定您不依赖于将来可能会损坏的东西......然后实现您自己的 tuple

【讨论】:

  • 请注意,这应该扩展到标准库定义的get 的其他用途。 arraypair 应该具有相同的保护。事实上,如果一个类型是可分解的(可用于结构化绑定),那么在访问不同的索引时调用的用户代码不得引发数据竞争。
  • @NicolBolas 我不太确定最后一点。我可以看到您希望不同索引对相同数据进行别名的情况。例如一个惰性求值的向量混合操作。
  • @Frank 这并不重要。 get 操作本身应该是线程安全的,并且可以访问从元组创建的数组的 元素。元素本身可能不一定是线程安全的——像指针一样可以指向同一个对象,因此通过指针写入不是线程安全的。
  • @SergeyA 我是说我可以构想出T 类型,其中float&amp; std::get&lt;0&gt;(T&amp;)float&amp; std::get&lt;1&gt;(T&amp;) 返回对同一个浮点对象的两个引用。如果您认为这应该没问题,那么 NicolBolas 建议中的“访问不同索引时”部分也可以删除。
  • @Frank: "例如惰性求值向量 swizzle 操作。" GLSL 的 swizzle 掩码明确禁止使用作为左值访问相同组件两次的左值 swizzle (vec.xyyz = ... 是编译错误)。因此,类似的 swizzle 操作将返回 T,而不是 T&amp;。这将使此类事情通过const&amp; 参数进行读取访问,因此不会引发数据竞争。
【解决方案2】:
template<class...Ts>
std::tuple< std::remove_reference_t<T>* >
to_pointers( std::tuple<Ts...>& );

template<class T0, class...Ts>
std::array<T0, 1+sizeof...(Ts)>
to_array( std::tuple<T0, Ts...> const& );

写下这些。

int main() {
    data_t landing;
    std::atomic<int> completed = 0;

    // Whichever thread pings last will be the one performing foo()
    auto ping = [&](){
        if(++completed == 4) {
            foo(landing);
        }
    };

    auto arr = to_array(to_pointers(landing));

    std::thread a([&](){ *arr[0] = 1; ping(); });
    std::thread b([&](){ *arr[1] = 2; ping(); });
    std::thread c([&](){ *arr[2] = 3; ping(); });
    std::thread d([&](){ *arr[3] = 4; ping(); });

    a.join();
    b.join();
    c.join();
    d.join();

    return 0;
}

我们通过指向元组的指针而不是通过std::get 访问元组元素。问题出在std::get的规范中;一旦您拥有指向保证存在于元组中的独立对象的独立指针,您就不会出现竞争条件。

所以我们将元组转换为一个线程中的指针数组(基本上是免费的),然后在线程中安全地使用它。

【讨论】:

  • 但是现在你已经引入了间接性,你需要编译器来优化它。
  • 这也是这个 MCVE 的一个有效解决方案,但是如果异步操作的范围超出了原始调用函数,那么这些指针将不得不在登陆的同时进行堆分配。
  • @Frank 当然,或者你可以让线程按值捕获它们的指针。 [ptr = arr[0]]{ *ptr = 1; ping(); } 。这一点是双重的; (1) 相同类型的元组实际上是 std::array,并且 (2) 将引用或指针存储到元组中是合法的,即使 std::get&lt;I&gt; 不是无竞争的。
猜你喜欢
  • 1970-01-01
  • 2012-11-15
  • 2020-02-17
  • 1970-01-01
  • 2014-10-08
  • 2017-07-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多