4

我正在开发一个C++应用程序Qt。我有一个非常基本的疑问,如果这太愚蠢了,请原谅我......

我应该创建多少个线程才能在最短的时间内在它们之间划分任务?

我问这个是因为我的笔记本电脑是第三代i5处理器(3210m)。因此,由于它是双核NO_OF_PROCESSORS 环境变量,因此向我展示了4。我在一篇文章中读到,应用程序的动态内存仅适用于启动该应用程序的处理器。那么我应该只创建1 个线程(因为 env 变量说 4 个处理器)还是2 个线程(因为我的处理器是双核和 env 变量可能暗示核心数)还是4 个线程(如果那篇文章是错误的)?请原谅我,因为我是一个尝试学习 Qt 的初级程序员。谢谢你 :)

4

4 回答 4

4

虽然超线程有点谎言(你被告知你有 4 个核心,但你实际上只有 2 个核心,另外两个只运行在前两个不使用的资源上,如果有这样的话),正确的做法仍然是使用尽可能多的NO_OF_PROCESSORS线程

请注意,英特尔并不是唯一对您撒谎的公司,在最近的 AMD 处理器上更糟糕的是,您拥有 6 个所谓的“真正”内核,但实际上只有 4 个内核,它们之间共享资源。

然而,大多数时候,它或多或少都有效。即使没有显式阻塞线程(在等待函数或阻塞读取上),也总会有一个核心停止的点,例如由于缓存未命中而访问内存时,这会释放出可以被超线程核心。

因此,如果您有很多工作要做,并且可以很好地并行化,那么您应该拥有与广告内核一样多的工作人员(无论它们是“真实的”还是“超级”)。这样,您可以最大限度地利用可用的处理器资源。

理想情况下,应在应用程序启动的早期创建工作线程,并有一个任务队列将任务交给工作人员。由于同步通常是不可忽略的,因此任务队列应该相当“粗略”。在最大核心使用率和同步开销之间存在折衷。

例如,如果您在一个数组中有 1000 万个元素要处理,您可能会推送引用 100,000 或 200,000 个连续元素的任务(您不会想要推送 1000 万个任务!)。这样,您可以确保平均而言没有内核处于空闲状态(如果一个内核提前完成,它会拉另一个任务而不是什么都不做),并且您只有大约一百个同步,其开销或多或少可以忽略不计。

如果任务涉及文件/套接字读取或其他可能无限期阻塞的事情,那么产生另外 1-2 个线程通常是没有错误的(需要一些实验)。

于 2013-01-26T16:44:47.780 回答
2

这完全取决于您的工作负载,如果您的工作负载非常密集,您应该更接近您的 CPU 拥有的线程数(在您的情况下为 4 - 2 核 * 2 用于超线程)。少量的超额订阅也可以,因为这可以补偿您的一个线程等待锁定或其他东西的时间。
另一方面,如果您的应用程序不依赖于 cpu 并且大部分时间都在等待,您甚至可以创建比 cpu 计数更多的线程。但是,您应该注意到创建线程可能是相当大的开销。唯一的解决方案是衡量您的瓶颈是否存在并朝该方向进行优化。

另请注意,如果您使用的是 c++11,您可以使用std::thread::hardware_concurrency一种可移植的方式来确定您拥有的 cpu 内核数量。

关于你关于动态内存的问题,你一定误解了那里的东西。通常你创建的所有线程都可以访问你在应用程序中创建的内存。另外,这与C++无关,超出了C++标准的范围。

于 2013-01-26T16:46:20.840 回答
1

NO_OF_PROCESSORS显示 4,因为您的 CPU 具有超线程。超线程是英特尔的技术商标,它使单个内核能够或多或少地同时执行同一应用程序的 2 个线程。只要一个线程正在获取数据而另一个线程正在访问 ALU,它就可以工作。如果两者都需要相同的资源并且指令无法重新排序,则一个线程将停止。这就是您看到 4 个核心的原因,即使您有 2 个。

动态内存仅可用于其中一个内核,IMO 不太正确,但寄存器内容和有时缓存内容是正确的。驻留在 RAM 中的所有内容都应该可供所有 CPU 使用。

比 CPU 更多的线程可以提供帮助,具体取决于您的操作系统调度程序的工作方式/您访问数据的方式等。要找到您必须对代码进行基准测试。其他一切都只是猜测。

除此之外,如果您正在尝试学习 Qt,那么担心这可能不是正确的事情......

编辑:

回答您的问题:如果您增加线程数,我们无法真正告诉您程序运行的速度有多慢/多快。取决于你在做什么,这会改变。例如,如果您正在等待来自网络的响应,则可以更多地增加线程数。如果您的线程都使用相同的硬件,4 个线程的性能可能不会比 1 个更好。最好的方法是简单地对您的代码进行基准测试。

理想的世界中,如果您“只是”在运行 4 或 8 个线程时处理数字不应该有什么不同,那么净时间应该是相同的(忽略上下文切换的时间等),只是响应时间会有所不同。问题是没有什么是理想的,我们有缓存,你的 CPU 都在同一条总线上访问相同的内存,所以最终它们会竞争对资源的访问。然后,您还有一个操作系统,它可能会或可能不会在给定时间安排线程/进程。

您还要求解释同步开销:如果您的所有线程都访问相同的数据结构,您将不得不进行一些锁定等操作,以便在更新时没有线程访问处于无效状态的数据。

假设你有两个线程,都做同样的事情:

int sum = 0; // global variable

thread() {
    int i = sum;
    i += 1;
    sum = i;
}

如果同时启动两个线程执行此操作,则无法可靠地预测输出:它可能会像这样发生:

THREAD A : i = sum; // i = 0
           i += 1;  // i = 1
**context switch**
THREAD B : i = sum; // i = 0
           i += 1;  // i = 1
           sum = i; // sum = 1
**context switch**
THREAD A : sum = i; // sum = 1

最后sum12即使你启动了两次线程。为避免这种情况,您必须同步sum对共享数据的访问。通常,您可以通过根据需要阻止访问来做到这一点sum。同步开销是线程等待资源再次解锁、什么都不做的时间。

如果每个线程都有离散的工作包并且没有共享资源,则应该没有同步开销。

于 2013-01-26T16:47:08.497 回答
0

在 Qt 中开始在线程之间划分工作的最简单方法是使用 Qt Concurrent 框架。示例:您想要对 QList 中的每个项目执行一些操作(很常见)。

void operation( ItemType & item )
{
  // do work on item, changing it in place
}

QList<ItemType> seq;  // populate your list

// apply operation to every member of seq
QFuture<void> future = QtConcurrent::map( seq, operation );
// if you want to wait until all operations are complete before you move on...
future.waitForFinished();

Qt 自动处理线程...无需担心。QFuture文档描述了如何在map需要时使用信号和槽不对称地处理完成。

于 2013-01-26T21:57:26.480 回答