使用 std::threads 的正确语法

proper syntax for using std::threads

提问人:user3116936 提问时间:1/17/2023 最后编辑:user3116936 更新时间:1/18/2023 访问量:109

问:

我正在用C++实现一个并行累加器类。该类的实现如下:

#include <iostream>
#include <thread>
#include <cstring>
#include "cblas.h"

class ParallelAccumulator {
public:
    int num_contributions;
    double** contributions;
    int* is_contributing;
    int num_elements;
    
    ParallelAccumulator(int num_contributions, int num_elements) {
        this->num_contributions = num_contributions;
        this->num_elements = num_elements;
        contributions = new double*[num_contributions];
        is_contributing = new int[num_contributions];
        for (int i = 0; i < num_contributions; i++) {
            contributions[i] = new double[num_elements];
            is_contributing[i] = 0;
        }
    }
    
    void reset() {
        for (int i = 0; i < num_contributions; i++) {
            is_contributing[i] = 0;
        }
    }
    
    void zero_contributions() {
        for (int i = 0; i < num_contributions; i++) {
            memset(contributions[i], 0, num_elements * sizeof(double));
        }
    }
    
    int check_out_contribution() {
        for (int i = 0; i < num_contributions; i++) {
            if (is_contributing[i] == 0) {
                is_contributing[i] = 1;
                return i;
            }
        }
        return -1;
    }
    
    void check_in_contribution(int contrib_index) {
        is_contributing[contrib_index] = 0;
    }
    
    void reduce(double* output) {
        for (int i = 0; i < num_contributions; i++) {
            if (is_contributing[i] == 1) {
                cblas_daxpy(num_elements, 1.0, contributions[i], 1, output, 1);
            }
        }
    }
    
    ~ParallelAccumulator() {
        for (int i = 0; i < num_contributions; i++) {
            delete[] contributions[i];
        }
        delete[] contributions;
        delete[] is_contributing;
    }
};

但是,当我创建线程来测试类时,我遇到了编译问题,如下所示:

void test_function(ParallelAccumulator& accumulator, double* output, int id) {
    int contrib_index = accumulator.check_out_contribution();
    if (contrib_index == -1) {
        std::cout << "Error: no available contrib arrays" << std::endl;
        return;
    }
    double* contrib = accumulator.contributions[contrib_index];
    for (int i = 0; i < accumulator.num_elements; i++) {
        contrib[i] = id;
    }
    accumulator.check_in_contribution(contrib_index);
}

int main() {
    int num_contributions = 4;
    int num_elements = 10;
    double output[num_elements];
    ParallelAccumulator accumulator(num_contributions, num_elements);
    /* problematic code start here  */
    std::thread t1(test_function, std::ref(accumulator), output, 1);
    std::thread t2(test_function, std::ref(accumulator), output, 2);
    std::thread t3(test_function, std::ref(accumulator), output, 3);
    std::thread t4(test_function, std::ref(accumulator), output, 4);
    /* problematic code end here  */
    t1.join();
    t2.join();
    t3.join();
    t4.join();
    accumulator.reduce(output);
    for (int i = 0; i < num_elements; i++) {
        std::cout << output[i] << " ";
    }
    std::cout << std::endl;
    return 0;
}

编译错误包括:

parallel_accumulator.cpp:87:67: error: no matching function for call to 'std::thread::thread(void (&)(ParallelAccumulator&, double*, int), std::reference_wrapper<ParallelAccumulator>, double [num_elements], int)'    87 |     std::thread t1(test_function, std::ref(accumulator), output, 1);
      |                                                                   ^ In file included from /usr/local/Cellar/gcc/11.3.0_2/include/c++/11/thread:43,
                 from parallel_accumulator.cpp:2: /usr/local/Cellar/gcc/11.3.0_2/include/c++/11/bits/std_thread.h:127:7: note: candidate: 'template<class _Callable, class ... _Args, class> std::thread::thread(_Callable&&, _Args&& ...)'   127 |       thread(_Callable&& __f, _Args&&... __args)
      |       ^~~~~~ /usr/local/Cellar/gcc/11.3.0_2/include/c++/11/bits/std_thread.h:127:7: note:   template argument deduction/substitution failed: parallel_accumulator.cpp:87:67: note:   variable-sized array type 'double (&)[num_elements]' is not a valid template argument    87 |    std::thread t1(test_function, std::ref(accumulator), output, 1);
      |                                                                   ^ In file included from /usr/local/Cellar/gcc/11.3.0_2/include/c++/11/thread:43,
                 from parallel_accumulator.cpp:2: /usr/local/Cellar/gcc/11.3.0_2/include/c++/11/bits/std_thread.h:157:5: note: candidate: 'std::thread::thread(std::thread&&)'   157 |     thread(thread&& __t) noexcept
      |     ^~~~~~ /usr/local/Cellar/gcc/11.3.0_2/include/c++/11/bits/std_thread.h:157:5: note:   candidate expects 1 argument, 4 provided /usr/local/Cellar/gcc/11.3.0_2/include/c++/11/bits/std_thread.h:121:5: note: candidate: 'std::thread::thread()'   121 |     thread() noexcept
= default;
      |     ^~~~~~

修复错误的正确语法是什么?我可以对此实现进行哪些修改以使其正常工作并具有尽可能大的灵活性?

C++ 多线程标准

评论

2赞 463035818_is_not_an_ai 1/17/2023
错误的关键部分是“注意:可变大小的数组类型'double(&)num_elements]'不是有效的模板参数”。 不是标准的C++,你不应该期望它与所有标准的C++兼容。用于动态调整大小的数组double output[num_elements];std::vector
1赞 463035818_is_not_an_ai 1/17/2023
不过,您不需要 VLA,只需将尺寸设为尺寸即可:int num_elements = 10; -> const int num_elements = 10;
0赞 463035818_is_not_an_ai 1/17/2023
我认为其余的都可以,所以关闭作为欺骗
1赞 463035818_is_not_an_ai 1/17/2023
如果您使用 gcc,我建议不时(或总是)编译,因为 gcc 允许在默认设置中出现一些奇怪的东西-pedantic-errors
3赞 Konrad Rudolph 1/17/2023
而不是指针,/ 你可以用它来大大简化你的代码。newdeletememsetstd::vector

答:

-1赞 user3116936 1/17/2023 #1

非常感谢上述讨论的所有参与者,特别是@doron。语法正确的代码如下:

    constexpr int num_elements = 10;
    std::thread t1(test_function, std::ref(accumulator), &output[0], 1);
    std::thread t2(test_function, std::ref(accumulator), &output[0], 2);
    std::thread t3(test_function, std::ref(accumulator), &output[0], 3);
    std::thread t4(test_function, std::ref(accumulator), &output[0], 4);
    

评论

1赞 mch 1/17/2023
这只能掩盖实际问题(无缘无故地使用非标准的可变长度数组)。只需制作一个 .num_elementsconstexpr int
0赞 Yksisarvinen 1/17/2023
您从第一个代码段复制了未修改的代码。我猜你想发布第二个片段的更新作为你的答案?
0赞 mch 1/17/2023
@Yksisarvinen不,他更改为 in lines,这样对象就不会得到 VLA,而是指向它的指针。output&output[0]std::threadstd::thread
0赞 user3116936 1/17/2023
@mch 谢谢你的建议,你能在代码中告诉我正确实施你的建议是什么意思吗?
0赞 user3116936 1/17/2023
@mch 答案中的代码是否正确解决了VLA问题?如何以最好的方式解决它?
-1赞 Michaël Roy 1/18/2023 #2

有几种方法可以调用 std::thread::thread(),如有疑问,请使用最简单的一种 lambda。

这肯定不是最迂腐的答案,但它不会惩罚性能,并且总是有效的。

// quick and efficient
std::thread t1([&]() { test_function(accumulator), &output[0], 1); });
// etc...

// If you do not like vague lambda captures
std::thread t1([&accumulator, &output]() { test_function(accumulator), &output[0], 1); });
// etc...

评论

0赞 user3116936 1/19/2023
这个提议没有回答我的具体问题。
0赞 Michaël Roy 1/19/2023
@user3116936 很抱歉说得这么具体,但问题是:“修复错误的正确语法是什么?我可以对此实现进行哪些修改以使其正常工作并具有尽可能大的灵活性?这些问题没有一个具体的答案,正如我的回答中所述,这是其中之一。
0赞 user3116936 1/19/2023
您真的认为将 lambda 与可能模糊的 lambda 捕获一起使用是最佳选择吗?我不认为这是一个更可持续且更易于维护的解决方案。
0赞 Michaël Roy 1/19/2023
我相信使用 lambda 是最实用的解决方案(一个统一的接口),也是调用 std::thread::thread() 的最灵活的可用方法,正如所问的那样。至于它是可持续的,并且易于维护,它和任何一样好。什么会让它比任何其他语法更难维护?我会提醒您,如前所述,这不是启动线程的最迂腐的方式,但是,您必须就此问题在本网站上寻求帮助,这表明使用 lambda 也可能比其他调用 std::thread::thread() 的方法更容易阅读和推理。