C ++的简单多线程混淆

时间:2013-01-26 16:38:35

标签: c++ multithreading qt qthread

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

我应该创建多少个线程来将任务划分到最短时间内?

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

4 个答案:

答案 0 :(得分:4)

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

请注意,英特尔并不是唯一一个骗你的人,在最近的AMD处理器中,你有6个所谓的“真正”核心更糟糕,但实际上只有4个,其中资源共享。

然而,大多数时候,它或多或少都有效。即使没有明确地阻塞线程(在等待函数或阻塞读取上),也总是存在一个核心停滞的点,例如由于高速缓存未命中而访问内存,这会释放出可供资源使用的资源。超线程核心。

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

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

例如,如果要处理的数组中有1000万个元素,则可能会推送引用100,000或200,000个连续元素的任务(您 想要推送1000万个任务!)。这样,你确保平均没有核心保持空闲(如果一个人提前完成,它会拉动另一个任务而不是什么都不做)而你只有一百个左右的同步,其开销或多或少都是可以忽略不计的。 / p>

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

答案 1 :(得分:2)

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

另请注意,如果您使用的是c ++ 11,则可以使用std::thread::hardware_concurrency以便携方式确定您拥有的cpu核心数。

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

答案 2 :(得分:1)

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

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

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

除此之外,如果你想学习Qt,这可能不是值得担心的事情......

修改

回答你的问题:如果增加线程数,我们无法告诉你程序运行速度会慢多少。根据你正在做的事情,这将改变。如果你是等待来自网络的响应,您可以更多地增加线程数。如果您的线程都使用相同的硬件,则4个线程的性能可能不会超过1.最好的方法是简单地对代码进行基准测试。

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

您还要求解释同步开销:如果所有线程都访问相同的数据结构,则必须执行某些锁定等操作,以便在更新时没有线程访问处于无效状态的数据。 / p>

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

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

最后,sum1,而不是2,即使你开始了两次线程。 为避免这种情况,您必须同步对共享数据sum的访问权限。通常,只要需要阻止对sum的访问,您就可以这样做。同步开销是线程等待资源再次解锁,什么都不做的时间。

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

答案 3 :(得分: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 documenation描述了如果需要,可以使用信号和插槽非对称地处理map完成。