我试图了解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;
}
您的数组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()
感兴趣,以找出有关硬件支持的真正并发线程数的提示,以免在太多上下文切换中创建太多线程和松散性能。你应该这样做。必须使用将运行的任务和参数初始化线程。当你创建一个包含100个线程的数组时,它会将它们全部初始化为什么,这是一种无用的浪费。另外,你使用sizeof
是错误的。 sizeof
将以字节为单位给出数据结构的原始大小,并且不会为您提供数组元素的数量。如果你想使用sizeof
来获得数组元素的数量,你应该做一些类似sizeof(array) / sizeof(<element type>)
的事情,在你的情况下是sizeof(list) / sizeof(int)
。但是,实际上,你可能不应该在C ++中使用C风格的数组,而且在这种情况下肯定不会。
你应该在运行时建立一个vector
并使用emplace_back
逐个创建线程。此外,您正在以非常C-ish的方式编写代码。你应该写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数量,或者操作系统将浪费大量时间在忙线程之间切换。以这种方式限制线程将涉及使用::std::thread::hardware_concurrency
等函数来查询可用的内核数量,并使用该信息来决定运行时有多少线程。
当然,这并不总是最简单的编写程序的方法,为简单起见,您可以选择任意数量的线程并坚持使用它。但是,如果你这样做,它应该是你可以逃脱的一个小数字。
但是,你创建线程的骑士方法,以及你在创建它之后使用每个线程进行join
ed的方式告诉我你并没有真正理解线程的确切做法。如果您立即使用线程join
,则线程不会同时运行。你正在启动它,然后在开始下一个之前立即等待它完成。
此外,您正在使用线程执行的微小任务是对它们的使用不当。线程创建起来有些昂贵。每次调用时都会创建一个线程的函数调用有数十,数百甚至数千微秒的开销。这听起来不是很多时间,但你必须记住,典型的函数调用开销就像是1/50微秒或甚至1/100微秒。因此,通过创建线程调用函数的开销是正常调用函数的数万倍。
这意味着您应该在一个线程中执行相当大的任务。如果任务至少花费一毫秒的时间,则不应创建线程。理想情况下,您应该创建一个线程,然后使用线程安全队列来发送它。这将减少每件事的开销。由于其开销要小得多,因此您可以经济地在线程中执行较小的任务。
当您只是尝试使用一个小程序来创建线程时,所有这些都需要考虑。但是,编写糟糕的多线程程序对世界造成了可怕的影响,尤其是对自己而言。除了学习线程接口的基础知识之外,在使用它们之前,您应该彻底了解它们。它们是一种很容易被误用的工具。
在解决问题时运行尽可能多的线程可能会导致程序上下文切换很多,因此无法尽快解决。您通常不希望运行的线程多于硬件支持的线程数(通常少一个)。
另一件经常让世界变得与众不同的是false sharing,它可以大大降低你的表现。
如果您使用的是支持新C ++ 17执行策略的编译器(如VS2017或g ++ 9),您可以使用并行执行策略for_each
执行std::execution::par
循环来完成您的工作。
下面的例子(我已经增加了很多工作量)在我的计算机上花了3.2秒,当我合理地确保避免错误共享(使用alignas(std::hardware_destructive_interference_size))和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";
}