我刚刚审查了一些非常糟糕的代码,这些代码通过创建新线程来在串行端口发送消息,为每个发送的消息创建一个新线程来打包和组装消息。是的,针对每个消息都会创建一个pthread,正确设置位,然后线程终止。我不知道为什么有人会这样做,但它引发了一个问题 - 实际上创建线程时有多少开销?
我刚刚审查了一些非常糟糕的代码,这些代码通过创建新线程来在串行端口发送消息,为每个发送的消息创建一个新线程来打包和组装消息。是的,针对每个消息都会创建一个pthread,正确设置位,然后线程终止。我不知道为什么有人会这样做,但它引发了一个问题 - 实际上创建线程时有多少开销?
#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),正在进行数据库重建的低速笔记本电脑(Intel Core i5-2540M)上连续运行了三次。三次连续运行的结果分别是:5.647秒、5.515秒和5.561秒。因此,在这台机器上,每个线程大约需要10微秒,可能在您的机器上要少得多。
考虑到串行端口的最大速率约为每10微秒1比特,这并不是什么大的开销。当然,有各种其他的线程损失,比如传递/捕获参数(虽然函数调用本身也会带来一些影响),缓存在核心之间的减速(如果在不同核心上的多个线程同时争夺相同的内存),等等。但总的来说,我非常怀疑您提出的用例会对性能产生负面影响(并且可能会带来好处),尽管您已经预先将这个概念标记为“真的很糟糕的代码”,甚至不知道启动线程需要多长时间。
是否使用它取决于您的具体情况。调用线程还负责什么?准备和写出数据包涉及到什么?它们的写出频率是多少(以何种方式分布?均匀分布、聚集分布等等...)以及它们的结构如何?系统有多少个核心?根据具体情况,最佳解决方案可能从“根本不需要线程”到“共享线程池”到“每个数据包一个线程”任何地方都可以。
请注意,线程池并非魔法,有些情况下,它们会比独立的线程慢,因为线程的最大减速之一是同步缓存内存,而同时必须从不同的线程中查找并处理更新的线程池必须这样做。因此,如果处理器不确定其他进程是否改变了某个内存部分,则主线程或子处理线程中的一个可能会被卡住等待。相比之下,在理想情况下,对于给定任务的唯一处理线程只需要与其调用任务共享内存一次(在启动时),然后它们再也不会相互干扰。
一直以来我听说创建线程是非常便宜的,尤其是与创建进程的替代方案相比。如果您所讨论的程序没有很多需要同时运行的操作,则可能不需要使用线程,根据您的描述,这很可能是情况。一些支持我的文献:
http://www.personal.kent.edu/~rmuhamma/OpSystems/Myos/threads.htm
从以下几个方面看,线程是一种廉价的方式:
他们只需要一个堆栈和寄存器存储空间,因此线程创建成本低。
线程在工作的操作系统中使用非常少的资源。也就是说,线程不需要新的地址空间、全局数据、程序代码或操作系统资源。
当与线程一起工作时,上下文切换速度很快。原因是我们只需要保存和/或恢复PC、SP和寄存器。
更多类似内容请点击这里.
在《操作系统概念第8版》(第155页)中,作者写到了使用线程的好处:
为进程分配内存和资源是昂贵的, 因为线程共享所属进程的资源,创建和上下文切换线程更经济。在实际中,很难量化开销的差异,但通常创建和管理进程比线程耗时更长。例如,在Solaris中,创建进程的速度大约比创建线程慢30倍,上下文切换慢五倍。
线程创建会有一些开销,但与串口的通常较慢的波特率(19200比特/秒是最常见的)相比,这并不重要。
你绝对不想这样做。创建一个单一的线程或一个线程池,当有消息可用时只需发送信号。接收到信号后,线程可以执行任何必要的消息处理。
就开销而言,线程的创建/销毁,特别是在Windows上,是相当昂贵的。具体来说,大约需要数十微秒的时间。它应该主要在应用程序的开始/结束时完成,可能会有动态调整大小的线程池的例外情况。
创建线程和在线程中进行计算非常昂贵:所有数据结构都需要设置,线程必须向内核注册并进行线程切换,以便新线程实际上得到执行(在未指定且不可预测的时间内)。执行thread.start
并不意味着立即调用线程主函数。
正如 typoking 提到的文章指出,与进程创建相比,线程的创建仅较为廉价。总体而言,它是相当昂贵的。
我永远不会使用线程
在您的示例中,创建一个处理所有串行通信并永久存在的线程是有意义的(正如已经指出的那样)。
为了比较,我们来看一下OSX:链接
内核数据结构:大约1 KB 堆栈空间:512 KB (次要线程):8 MB (OS X主线程),1 MB (iOS主线程)
创建时间:大约90微秒
我猜posix线程的创建时间应该也是这个范围内。
open
和read
这样熟悉的系统调用的数量相同。我在我的系统上进行了一些随意的测量,显示pthread_create
所需的时间大约是open(“/dev/null”,O_RDWR)
的两倍,这与纯计算相比非常昂贵,但与涉及用户和内核空间之间切换的任何IO或其他操作相比则非常便宜。