创建线程时有多少开销?

时间:2010-10-14 03:03:50

标签: c++ pthreads posix

我刚刚回顾了一些非常糟糕的代码 - 通过创建一个新线程在串行端口上发送消息的代码,用于在每个发送的单个消息的新线程中打包和组装消息。是的,对于创建pthread的每条消息,正确设置了位,然后线程终止。我不知道为什么有人会做这样的事情,但它提出了一个问题 - 实际创建一个线程有多少开销?

12 个答案:

答案 0 :(得分:36)

为了复活这个旧线程,我只做了一些简单的测试代码:

#include <thread>

int main(int argc, char** argv)
{
  for (volatile int i = 0; i < 500000; i++)
    std::thread([](){}).detach();
  return 0;
}

我用g++ test.cpp -std=c++11 -lpthread -O3 -o test编译了它。然后我在旧的(内核2.6.18)负载很重(做数据库重建)慢速笔记本电脑(英特尔酷睿i5-2540M)上连续运行了三次。连续三次运行的结果:5.647s,5.515s和5.561s。所以我们在这台机器上看到每个线程超过10微秒的时间,可能更少在你的机器上。

根据串行端口每10微秒大约1位,最多只有很多开销。现在,当然可以获得涉及传递/捕获参数的各种额外线程丢失(虽然函数调用本身可以强加一些),核心之间的缓存减速(如果不同核心上的多个线程同时在同一内存上争夺),但总的来说,我非常怀疑你提出的用例会对性能产生负面影响(并且可能会带来好处,取决于),尽管你已经先发制人地将这个概念标记为“非常糟糕的代码”而不知道需要多少时间发起一个帖子。

这是否是一个好主意取决于你的情况的细节。还有什么是调用线程负责?准备和写出数据包究竟涉及什么?他们写出的频率(具有什么样的分布?统一,聚集等等?)以及它们的结构是什么样的?系统有多少个核心?等等。根据细节,最佳解决方案可以是从“无线程”到“共享线程池”到“每个数据包的线程”的任何地方。

请注意,线程池并不神奇,在某些情况下可能会比单独的线程更慢,因为线程最大的减速之一是同时同步多个线程使用的缓存内存,并且线程池由它们非常必须从不同的线程中查找和处理更新的性质必须这样做。因此,如果处理器不确定其他进程是否更改了一段内存,那么主线程或子处理线程可能会被迫等待。相比之下,在理想情况下,给定任务的唯一处理线程只需与其调用任务共享一次内存(当它启动时),然后它们不会再次相互干扰。

答案 1 :(得分:11)

我一直被告知线程创建很便宜,特别是与创建进程的替代方法相比。如果你正在谈论的程序没有很多需要同时运行的操作,那么可能没有必要进行线程化,并且根据你所写的内容判断这可能就是这种情况。一些文献支持我:

http://www.personal.kent.edu/~rmuhamma/OpSystems/Myos/threads.htm

  

在某种意义上,线程很便宜

     
      
  1. 他们只需要一个堆栈和存储寄存器因此,线程创建起来很便宜。

  2.   
  3. 线程使用非常少的操作系统资源   他们正在工作。那是,   线程不需要新的地址空间,   全球数据,程序代码或操作   系统资源。

  4.   
  5. 使用线程时,上下文切换速度很快。原因是   我们只需要保存和/或   恢复PC,SP和寄存器。

  6.   

更多相同的here

Operating System Concepts 8th Edition(第155页)中,作者写了关于线程的好处:

  

为流程创建分配内存和资源的成本很高。因为   线程共享资源   他们所属的过程就是这样   更经济的创造和   上下文切换线程。经验   衡量开销的差异可以   很难,但总的来说是   更多的时间来创建和创建   管理流程而不是线程。在   例如,Solaris创建一个   过程大约慢了三十倍   而不是创建一个线程和上下文   切换速度慢了五倍。

答案 2 :(得分:11)

你绝对不想这样做。创建单个线程或线程池,并在消息可用时发出信号。收到信号后,线程可以执行任何必要的消息处理。

就开销而言,线程创建/销毁(尤其是在Windows上)相当昂贵。大概在几十微秒的某个地方,具体而言。在大多数情况下,它应该仅在应用程序的开始/结束时完成,可能的例外是动态调整大小的线程池。

答案 3 :(得分:8)

线程创建有一些开销,但将它与串口通常较慢的波特率(最常见的19200位/秒)进行比较,这无关紧要。

答案 4 :(得分:7)

  
    

...在串口发送消息...对于创建pthread的每条消息,正确设置位,然后线程终止。 ......实际创建线程时有多少开销?

  

这是高度系统特定的。例如,上次我使用VMS线程是噩梦般缓慢(多年来,但是从内存中,一个线程可以创建每秒10个以上的东西(如果你保持这种状态几秒钟没有线程退出你的核心),而在Linux上你可能会创造数千个。如果您想确切地知道,请在您的系统上进行基准测试。但是,仅仅知道更多关于消息的信息并不多见:它们是平均5个字节还是100k,它们是连续发送还是线路闲置,以及应用程序的延迟要求都是相关的代码的线程使用的适当性作为线程创建开销的任何绝对度量。并且性能可能不需要成为主要的设计考虑因素。

答案 5 :(得分:3)

线程中的线程创建和计算非常昂贵。 需要设置所有数据结构,必须发生在内核中注册的线程和线程切换,以便新线程实际执行(在未指定且不可预测的时间内)。 执行thread.start并不意味着立即调用线程main函数。 正如文章(由typoking所述)指出,与创建流程相比,创建线程的成本很低。总的来说,它非常昂贵。

我永远不会使用线程

  • 进行简短的计算
  • 一个计算,我需要在我的代码流中得到结果(那个 意思是,我开始了线程 等待它返回结果 这是计算

在你的例子中,创建一个处理所有串行通信并且是永恒的线程是有意义的(正如已经指出的那样)。

HTH

马里奥

答案 6 :(得分:3)

为了进行比较,请看一下OSX:Link

  • 内核数据结构:大约1 KB堆栈空间:512 KB (辅助线程):8 MB(OS X主线程),1 MB(iOS主要 线程)

  • 创作时间:约90微秒

posix线程创建也应该围绕这个(不是很远的数字)我想。

答案 7 :(得分:2)

在任何理智的实现中,线程创建的成本应该与它涉及的系统调用的数量成比例,并且与熟悉的系统调用(例如openread的数量级相同。在我的系统上进行的一些随意测量显示pthread_create占用的时间是open("/dev/null", O_RDWR)的两倍,相对于纯计算而言,这是非常昂贵的,但相对于任何涉及用户和用户之间切换的IO或其他操作而言非常便宜。内核空间。

答案 8 :(得分:2)

我使用了以上&#34;可怕的&#34;在我制作的VOIP应用程序中进行设计。它工作得非常好......对于本地连接的计算机,绝对没有延迟或错过/丢失数据包。每次数据包到达时,都会创建一个线程并将该数据传递给输出设备。当然数据包很大,所以没有造成任何瓶颈。同时主线程可以循环返回等待并接收另一个传入的数据包。

我尝试过其他设计,其中我需要的线程是预先创建的,但这会造成它自己的问题。首先,您需要为线程正确设计代码,以检索传入的数据包并以确定的方式处理它们。如果您使用多个(预先分配的)线程,则可能无法处理数据包并且无序。如果您使用单个(预分配)线程来循环并获取传入的数据包,则线程可能会遇到问题并终止,不会留下任何线程来处理任何数据。

创建一个处理每个传入数据包的线程非常干净,特别是在多核系统和传入数据包很大的情况下。另外,为了更直接地回答您的问题,线程创建的替代方法是创建管理预分配线程的运行时进程。能够同步数据切换和处理以及检测错误可能会增加同样多的开销,如果不是更多的开销,只需创建一个新线程。这一切都取决于您的设计和要求。

答案 9 :(得分:2)

有趣。

我在FreeBSD PC上进行了测试,并得到以下结果:

FreeBSD 12-STABLE, Core i3-8100T, 8GB RAM: 9.523sec<br/>
FreeBSD 12.1-RELEASE, Core i5-6600K, 16GB: 8.045sec

您需要做

sysctl kern.threads.max_threads_per_proc=500100

尽管。

Core i3-8100T速度很慢,但结果差别不大。相反,CPU时钟似乎更相关:i3-8100T 3.1GHz和i5-6600k 3.50GHz。

答案 10 :(得分:1)

这确实取决于系统,我测试了@Nafnlaus代码:

#include <thread>

int main(int argc, char** argv)
{
  for (volatile int i = 0; i < 500000; i++)
    std::thread([](){}).detach();
  return 0;
}

在我的台式机Ryzen 5 2600上:

Windows 10,使用MSVC 2019版本进行编译,并在其周围添加了std :: chrono调用来计时。闲置(仅带有217个选项卡的Firefox):

花费了大约20秒(20.274、19.910、20.608)(在Firefox关闭的情况下,大约也需要20秒)

Ubuntu 18.04编译为:

g++ main.cpp -std=c++11 -lpthread -O3 -o thread

定时:

time ./thread

花了大约5秒钟(5.595、5.230、5.297)

我的树莓派3B上的相同代码编译为:

g++ main.cpp -std=c++11 -lpthread -O3 -o thread

定时:

time ./thread

花费大约15秒(16.225、14.689、16.235)

答案 11 :(得分:0)

正如其他人提到的,这似乎非常依赖操作系统。在我的运行 Win10 的 Core i5-8350U 上,它花费了 118 秒,这表明每个线程的开销约为 237 uS(我怀疑病毒扫描程序和所有其他安装的垃圾 IT 也确实减慢了它的速度)。双核至强 E5-2667 v4 运行 Windows Server 2016 需要 41.4 秒(每个线程 82 uS),但它也在后台运行大量 IT 垃圾,包括病毒扫描程序。我认为更好的方法是使用一个线程来实现一个队列,该线程不断处理队列中的任何内容,以避免每次创建和销毁线程的开销。