我正在尝试学习C ++中的多线程。我正在尝试通过使用线程数组来创建带有循环的100个线程,但出现错误。出现此错误:
error: array initializer must be an initializer list
thread threads[i](task1, list[i]);
代码如下:
static int list [100] = {};
thread threads [100] = {};
void task1(int n)
{
for (int i = 0; i < 10; i++)
n = n + 1;
}
int main()
{
for (int i = 0; i < sizeof(list); i++){
thread threads[i](task1, list[i]);
threads[i].join();
}
int total = 0;
for (int i = 0; i < sizeof(list); i++)
total += list[i];
cout << total << endl;
return 0;
}
答案 0 :(得分:4)
您应该以不同的方式进行操作。必须使用将要运行的任务和参数来初始化线程。当您创建一个由100个线程组成的数组时,它将一无所有地初始化它们,这是无用的浪费。另外,您对sizeof
的使用是错误的。 sizeof
将为您提供数据结构的原始大小(以字节为单位),而不会为您提供数组元素的数量。如果要使用sizeof
来获取数组元素的数量,则应该执行类似sizeof(array) / sizeof(<element type>)
的操作,在这种情况下,应为sizeof(list) / sizeof(int)
。但是,实际上,您可能不应该在C ++中使用C样式的数组,并且在这种情况下,最肯定的不是。
您应该在运行时构建一个vector
,并使用emplace_back
来一个一个地创建线程。另外,您正在以非常C的方式编写代码。您应该编写的是C ++,而不是C。(此外,总是喜欢使用前缀++
。在这里并没有多大关系,但是出于性能原因,并且有时候您很重要,如果您在始终使用前缀版本的习惯,您不会有任何问题。)这可能是这样的:
#include <vector>
#include <array>
#include <thread>
#include <iostream>
using ::std::thread;
using ::std::array;
using ::std::vector;
using ::std::cout;
using ::std::endl;
using ::std::ref;
static array<int, 100> list {};
vector<thread> threads;
void task1(int &n)
{
for (int i = 0; i < 10; ++i)
n = n + 1;
}
int main()
{
threads.reserve(list.size()); // Not needed, an optimization.
for (int &n : list) { // Use a range-based for loop, not an explicit counting loop
threads.emplace_back(task1, ::std::ref(n));
}
for (auto &thr : threads) {
thr.join();
}
int total = 0;
for (int const &n : list) {
total += n;
}
cout << total << endl;
return 0;
}
现在,由于这是一个玩具程序,因此我不会批评您仅随机创建100个线程的决定。实际上,这是一个坏主意。您想根据您拥有的CPU数量来调整创建的线程数,否则OS将浪费大量时间在繁忙线程之间进行切换。以这种方式限制线程将涉及使用::std::thread::hardware_concurrency
之类的函数来查询可用内核数,并使用该信息来决定运行时要拥有多少个线程。
当然,这并非总是最简单的编写程序的方法,为简单起见,您可以选择任意数量的线程并坚持使用。但是,如果这样做,它应该尽可能地小。
但是,您使用轻巧的方法创建线程以及创建后立即join
使用每个线程的方式告诉我,您实际上并不完全了解线程的功能。如果立即join
使用线程,则该线程不会同时运行。您正在启动它,然后立即等待它完成,然后再开始下一个。
此外,您正在执行线程的微小任务是对它们的不良使用。创建线程有些昂贵。每次调用一个涉及创建线程的函数调用都有数十,数百甚至数千微秒的开销。这听起来不花很多时间,但是您必须记住,典型的函数调用开销大约是1/50微秒甚至1/100微秒。因此,通过创建线程来调用函数的开销是正常情况下调用函数的数万倍。
这意味着您应该在线程中执行相当大的任务。如果任务花费的时间至少不超过一毫秒,则不应创建线程。理想情况下,您应该创建一个线程,然后使用线程安全队列将其发送给工作。这将减少每件事的开销。由于这样做的开销要小得多,因此您可以经济地在线程中执行较小的任务。
当您只是尝试使用一个小的程序来创建线程时,所有这些都需要很多。但是,写得不好的多线程程序对世界,尤其是对自己来说,是一件可怕的事情。除了学习线程接口的基础知识之外,您还应该在使用它们之前全面了解它们。它们是一种非常容易滥用的工具。
答案 1 :(得分:4)
您的数组thread threads [100] = {};
创建100个默认初始化的不活动线程。
您可以通过如下更改循环来替换这些默认线程:
for (int i = 0; i < sizeof(list); i++){
threads[i] = thread(task1, list[i]); // <---- valid syntax
threads[i].join();
}
这是说
vector<thread>
是一种更好的做法,以便仅在需要时构造线程(请参见其他答案)std::thread::hardware_concurrency()
感兴趣,以找到有关硬件支持的真正并发线程数的提示,以免在太多上下文切换中创建太多线程并降低性能。 答案 2 :(得分:1)
运行有尽可能多的线程来解决您的问题可能会导致您的程序进行上下文切换很多,因此无法尽快解决它。通常,您不希望运行的线程多于硬件所支持的线程(通常少于一个)。
false sharing常常会带来很大的改变,这可能会大大降低您的效果。
如果您使用的是支持新C ++ 17执行策略的编译器(例如VS2017或g ++ 9),则可以使用并行执行策略for_each
进行std::execution::par
循环,以执行工作。
下面的示例(在此我大大增加了工作量)在我已确保确保避免错误共享(使用alignas(std::hardware_destructive_interference_size))时在计算机上花费了3.2秒,而使用默认对齐方式则花费了21.3秒。
#include <iostream>
#include <array> // std::array
#include <execution> // std::execution::par
#include <new> // std::hardware_destructive_interference_size
struct bork {
alignas(std::hardware_destructive_interference_size) int n;
// int n; // default alignment
};
std::array<bork, 1000> list{ 0 };
int main() {
std::for_each(std::execution::par, list.begin(), list.end(), [](auto& b) {
for (int i = 0; i < 100000000; i++) b.n = b.n + 1;
}
);
long long total = 0;
for (const auto& b : list) total += b.n;
std::cout << total << "\n";
}