在C++中创建 100 个线程
Creating 100 threads in C++
我正在尝试学习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
的方式告诉我,您并不真正了解线程的作用。如果立即使用线程join
,则该线程不会并发运行。您正在启动它,然后立即等待它完成,然后再开始下一个。
此外,您让线程执行的小任务是对它们的不良使用。创建线程的成本有些高。每次调用时都涉及创建线程的函数调用具有数十、数百甚至数千微秒的开销。这听起来不像很多时间,但你必须记住,典型的函数调用开销大约是 1/50 微秒甚至 1/100 微秒。因此,通过创建线程调用函数的开销是正常调用函数的数万倍。
这意味着您应该在线程中执行相当大的任务。如果任务至少不需要一毫秒的时间,则不应创建线程。理想情况下,您应该创建一个线程,然后使用线程安全队列向其发送要执行的操作。这将大大减少每件事的开销。由于这样做的开销要小得多,因此您可以经济地在线程中执行较小的任务。
当您只是尝试使用一个小程序来创建线程时,所有这些都需要大量内容。但是,写得不好的多线程程序对世界来说是一件可怕的事情,尤其是对你自己。除了学习线程接口的基础知识之外,在使用它们之前,您还应该彻底了解它们。它们是一种非常容易滥用的工具。
运行尽可能多的线程,因为有问题要解决,可能会导致程序进行大量的上下文切换,因此不能尽快解决它。您通常不希望运行的线程数超过硬件支持的线程数(通常少一个线程)。
另一件经常产生世界差异的事情是虚假共享,这会大大降低您的表现。
如果您使用的是支持新的 C++17 执行策略(如 VS2017 或 g++ 9)的编译器,则可以使用并行执行策略std::execution::par
执行for_each
循环来完成您的工作。
下面的示例(我大大增加了工作量)在合理地确保避免错误共享(使用 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";
}
- 两个线程一个使用流 Api,另一个线程创建文件失败并出现错误ERROR_SHARING_VIOLATION
- C++ 线程创建/删除与线程停止/恢复
- Qt - 如何从线程创建 QFuture
- 我可以使用Qt线程ID为每个线程创建唯一的缓存吗?
- 零MQ 后台线程创建
- OpenMP 线程创建
- GLFW & ImGui:从 main 以外的线程创建 ImGui 控件
- 对象:无法为位于不同线程中的父线程创建子级
- C++ 11:线程创建给我一个"Attempt to use a deleted function"错误
- C 的周期性线程创建
- MPI - 当数组初始化值必须为常量时,如何为工作线程创建部分数组
- 当主GUI线程被阻塞时,如何从工作线程创建无模式对话框
- 多个线程创建5个线程来计算质数
- 为线程创建模板
- 线程创建,CRT和DLL是如何完成的?
- 同步线程创建和销毁(静态)对象
- 竞争条件:一个线程创建静态对象,另一个线程在初始化完成之前使用它.如何处理
- 从不同线程创建QMainWindow
- QFuture 无法为位于不同线程中的父线程创建子级
- ( QNativeSocketEngine)QObject:无法为位于不同线程中的父线程创建子级