为什么单个线程比多个线程更快,即使它们本质上具有相同的开销?

我在8核心处理器上运行64位Windows 7。 我跑了以下几点:

#include "stdafx.h" #include <iostream> #include <Windows.h> #include <process.h> #include <ctime> using namespace std; int count = 0; int t = time(NULL); //poop() loops incrementing count until it is 300 million. void poop(void* params) { while(count < 300000000) { count++; } cout<< time(NULL) - t <<" \n"; } int _tmain(int argc, _TCHAR* argv[]) { //_beginthread(poop, 0, NULL); //_beginthread(poop, 0, NULL); poop(NULL); cout<<"done"<<endl; while(1); return 0; } 

我比较了结果,当我取消了beginThread的注释。 事实certificate,单线程版本完成这个最快! 实际上,增加更多的线程会使得这个过程花费更长的时间。 计数3亿使得这个过程需要8+秒,我认为这足以排除函数beginThread +其他小的开销。

我做了一些研究,multithreading处理速度较慢的一般结论是开销。 但是在这种情况下,无论我运行多个线程还是单个线程,访问的variables数(存在于数据段中,因为它是预分配的variablesafaik)的次数是相等的。 所以基本上,开销(如果是开销问题)并不是来自访问全局variables比局部variables花费更多的事实。

看看我的任务pipe理器,单线程进程使用13%cpu(大约1/8核心),并添加线程增加CPU使用量的增量约1/8。 所以就CPU的能力而言,假设任务pipe理器正在准确地描述这个,添加线程使用更多的CPU。 这进一步混淆了我..我是如何使用更多的整体CPU,单独的核心,但整体需要更长的时间才能完成任务?

TLDR:这是为什么发生的

你的代码本质上是错误的。

count++是一个三步操作,读取该值,将其递增,然后将其存储回变量。
如果两个线程同时在同一个变量上运行count++ ,其中一个将覆盖另一个的变化。

因此,多线程版本将最终做额外的工作,因为每个线程都会破坏其他线程的进度。

如果你count一个局部变量,时间应该看起来更加正常。

或者,您可以使用一个互锁的增量,这是线程安全的,但有额外的开销来跨线程同步。

正如你原来的问题的一些评论者指出你有一个正确性和性能问题。 首先你所有的线程正在同时访问计数 。 这意味着没有保证线程实际上都会达到3亿。 你可以通过在你的poop函数中声明count来解决这个错误

 void poop(void* params) { int count = 0; while(count < 300000000) { count++; } cout<< time(NULL) - t <<" \n"; } 

请注意,这不是t的问题,因为它只是被线程读取,而不是写入。 然而,这是一个问题,因为你也从多个线程写的。

另外,正如在注释中指出的那样,所有的线程都访问一个单独的内存位置。 这意味着当一个线程更新计数保存它的缓存线时,它必须被刷新并重新加载。 这是非常低效的内存访问。 通常情况下,当你访问数组中的连续元素而不是单个变量时,会发生这种情况(坏主意,参见上文)。 解决这个问题的方法是填充数组以确保每个条目都是L1高速缓存行大小的确切倍数,这对于目标处理器来说显然是特定的。 另一个选择是重构你的算法, 每个线程处理大块连续的元素,或者每个线程访问元素的方式使得线程不访问相邻的位置。

在使用Windows时,您可能需要考虑在代码中使用更高层次的抽象,而不是Win32线程函数。 并行模式库符合这里的法案(就像英特尔的Threaded Building Blocks一样 )。

  concurrency::parallel_invoke( [=] { poop(nullptr); }, [=] { poop(nullptr); } ); 

这允许PPL在线程池上调度你的任务,而不是你的应用程序必须显式创建线程。

您也可以考虑,对于非常小的任务,启动额外线程的开销可能会超过并行运行的收益。