Linux中的线程与进程

时间:2009-04-30 15:26:56

标签: linux performance multithreading process

我最近听到一些人说在Linux中,使用进程而不是线程几乎总是更好,因为Linux在处理进程方面非常有效,并且因为存在很多问题(例如锁定)有线程。但是,我很怀疑,因为在某些情况下线程似乎可以带来相当大的性能提升。

所以我的问题是,当遇到线程和进程都能很好地处理的情况时,我应该使用进程还是线程?例如,如果我正在编写Web服务器,我应该使用进程或线程(或组合)吗?

16 个答案:

答案 0 :(得分:300)

Linux使用1-1线程模型,(对内核)没有进程和线程之间的区别 - 一切都只是一个可运行的任务。 *

在Linux上,系统调用clone克隆任务,具有可配置的共享级别,其中包括:

  • CLONE_FILES:共享相同的文件描述符表(而不是创建副本)
  • CLONE_PARENT:不要在新任务和旧任务之间建立父子关系(否则,孩子的getppid() =父母的getpid()
  • CLONE_VM:共享相同的内存空间(而不是创建COW副本)

fork()拨打clone(至少分享)pthread_create()来电clone(分享)。 **

fork因为复制表和为内存创建COW映射而花费的成本比pthread_create稍微多一点,但是Linux内核开发人员已经尝试(并成功)将这些成本降到最低。

在任务之间切换,如果它们共享相同的内存空间和各种表,将比不共享它们便宜一点,因为数据可能已经加载到缓存中。但是,即使没有共享任何内容,切换任务仍然非常快 - 这是Linux内核开发人员试图确保(并成功确保)的其他内容。

事实上,如果您使用的是多处理器系统,共享实际上可能对性能有益:如果每个任务在不同的处理器上运行,则同步共享内存非常昂贵。


*简化。 CLONE_THREAD导致信号传递被共享(需要CLONE_SIGHAND,共享信号处理程序表。)

**简体。存在SYS_forkSYS_clone系统调用,但在内核中,sys_forksys_clone都是围绕同一do_fork函数的非常薄的包装器是copy_process周围的薄包装。是的,术语processthreadtask在Linux内核中可以互换使用...

答案 1 :(得分:57)

Linux(实际上是Unix)为您提供了第三种选择。

选项1 - 处理

创建一个独立的可执行文件,用于处理应用程序的某些部分(或所有部分),并为每个进程单独调用它,例如:程序运行自己的副本以将任务委派给。

选项2 - 线程

创建一个独立的可执行文件,它以单个线程启动并创建其他线程来执行某些任务

选项3 - fork

仅在Linux / Unix下可用,这有点不同。分叉进程实际上是它自己的进程,它有自己的地址空间 - 孩子可以(通常)不会影响其父进程或兄弟地址空间(与线程不同) - 所以你得到了额外的鲁棒性。

但是,内存页面没有被复制,它们是写时复制的,因此通常使用的内存比你想象的要少。

考虑一个Web服务器程序,它包含两个步骤:

  1. 读取配置和运行时数据
  2. 提供页面请求
  3. 如果使用线程,则步骤1将完成一次,步骤2将在多个线程中完成。如果使用“传统”进程,则需要为每个进程重复步骤1和2,并且存储配置和运行时数据的内存重复。如果使用了fork(),则可以执行第1步,然后执行fork(),将运行时数据和配置保留在内存中,不受影响,不进行复制。

    所以真的有三种选择。

答案 2 :(得分:50)

这取决于很多因素。进程比线程更重,并且具有更高的启动和关闭成本。进程间通信(IPC)也比在线通信更难,更慢。

相反,进程比线程更安全,更安全,因为每个进程都在自己的虚拟地址空间中运行。如果一个进程崩溃或缓冲区溢出,它根本不会影响任何其他进程,而如果一个线程崩溃,它将关闭进程中的所有其他线程,如果一个线程有缓冲区溢出,它会打开所有线程中的安全漏洞。

因此,如果您的应用程序的模块可以在几乎没有通信的情况下独立运行,那么您应该使用流程,如果您能够承担启动和关闭成本。 IPC的性能影响将是最小的,您将对错误和安全漏洞稍微安全一些。如果您需要获得或拥有大量共享数据(例如复杂数据结构)的所有性能,请使用线程。

答案 3 :(得分:9)

其他人已经讨论了这些考虑因素。

也许重要的区别在于,与线程相比,Windows进程繁重且昂贵,而在Linux中,差异要小得多,因此方程在不同的点上进行平衡。

答案 4 :(得分:8)

曾经有过Unix,在这个老旧的Unix中,流程有很多开销,所以一些聪明人做的是创建线程,它们与父进程共享相同的地址空间,他们只需要减少上下文切换,这将使上下文切换更有效。

在当代Linux(2.6.x)中,进程的上下文切换与线程之间的性能差别不大(只有MMU的东西是线程的附加内容)。 共享地址空间存在问题,这意味着线程中的错误指针可能破坏父进程的内存或同一地址空间中的另一个线程。

进程受MMU保护,因此错误的指针只会导致信号11而不会损坏。

我一般会使用进程(在Linux中没有太多的上下文切换开销,但由于MMU导致的内存保护),但如果我需要一个实时的调度程序类,那就是pthreads,这是一个不同的茶。 / p>

为什么你认为线程在Linux上有如此大的性能提升?你有这方面的数据,还是仅仅是一个神话?

答案 5 :(得分:5)

你的任务有多紧密耦合?

如果他们可以彼此独立生活,那么就使用流程。如果他们互相依赖,那就使用线程。这样,您可以在不干扰其他任务操作的情况下终止并重启坏的进程。

答案 6 :(得分:4)

更复杂的是,有thread-local storage和Unix共享内存这样的东西。

线程局部存储允许每个线程具有单独的全局对象实例。我唯一一次使用它是在linux / windows上构建仿真环境,用于在RTOS中运行的应用程序代码。在RTOS中,每个任务都是一个具有自己地址空间的进程,在仿真环境中,每个任务都是一个线程(具有共享地址空间)。通过将TLS用于单例等事物,我们能够为每个线程创建一个单独的实例,就像在“真正的”RTOS环境下一样。

共享内存可以(显然)为您提供让多个进程访问同一内存的性能优势,但是需要以正确的方式同步进程的成本/风险。一种方法是让一个进程在共享内存中创建数据结构,然后通过传统的进程间通信(如命名管道)向该结构发送句柄。

答案 7 :(得分:3)

我必须同意你所听到的内容。当我们对集群(xhpl等)进行基准测试时,我们总是通过线程进程获得明显更好的性能。 </anecdote>

答案 8 :(得分:3)

线程/进程之间的决定取决于您将使用它的内容。 进程的一个好处是它具有PID并且可以在不终止父进程的情况下被终止。

对于Web服务器的真实示例,apache 1.3过去只支持多个进程,但在2.0中,他们添加了an abstraction,以便您可以在两者之间切换。 Comments seems to同意进程更强大,但线程可以提供更好的性能(除了窗口,其中进程的性能很糟糕而你只想使用线程)。

答案 9 :(得分:3)

在我最近使用LINUX的工作中,有一点需要注意的是库。如果您正在使用线程,请确保您可以跨线程使用的任何库都是线程安全的。这烧了我几次。值得注意的是,libxml2不是开箱即用的线程安全的。它可以使用线程安全编译,但这不是aptitude安装所能获得的。

答案 10 :(得分:2)

对于大多数情况,我更喜欢进程而不是线程。 当您具有相对较小的任务(进程开销&gt;&gt;每个划分的任务单元所花费的时间)并且需要它们之间的内存共享时,线程可能是有用的。想一个大阵列。 另外(offtopic),请注意,如果您的CPU利用率是100%或接近它,那么多线程或处理将没有任何好处。 (事实上​​它会恶化)

答案 11 :(得分:1)

线程->线程共享一个内存空间,它是CPU的抽象,是轻量级的。 进程->进程有自己的内存空间,它是计算机的抽象。 要并行化任务,您需要抽象一个CPU。 但是,在线程上使用进程的优点是安全性,稳定性,而线程使用的内存少于进程,并且提供的延迟更短。 就网络而言,例如chrome和firefox。 如果使用Chrome,则每个选项卡都是新进程,因此chrome的内存使用率高于firefox,而提供的安全性和稳定性优于firefox。 chrome提供的安全性更好,因为每个选项卡都是一个新进程,因此不同的选项卡无法监听到给定进程的内存空间。

答案 12 :(得分:1)

如果您想尽可能地创建一个纯进程,则可以使用clone()并设置所有克隆标志。 (或者省去打字的麻烦,然后拨打fork()

如果您想尽可能地创建一个纯线程,则可以使用clone()并清除所有克隆标志(或者省去打字工作并调用pthread_create()

有28个标志指示共享级别。这意味着您可以根据要共享的内容创建超过2.68亿种任务。

这是当我们说Linux不区分进程和线程时,而是暗示程序中的任何控制流作为任务的意思。不区分两者的基本原理是,不能唯一地定义超过2.68亿种口味!

因此,做出是否使用进程或线程的“完美决定”实际上是决定要克隆28种资源中的哪一种

enter image description here

答案 13 :(得分:0)

我认为每个人在回答您的问题方面都做得很好。我只是添加有关Linux中线程与进程的更多信息,以阐明和总结以前在内核上下文中的一些响应。因此,我的回应是关于Linux中内核特定的代码。根据Linux Kernel文档,线程与进程之间没有明显的区别,除了线程使用与进程不同的共享虚拟地址空间。另请注意,Linux内核通常使用术语“任务”来指代进程和线程。

“没有内部结构来实现进程或线程,而是有一个结构task_struct描述了称为任务的抽象调度单元”

根据Linus Torvalds的说法,您根本不应该考虑进程与线程之间的关系,因为它太局限了,唯一的区别是在“将地址空间与父地址分开”或共享地址空间方面,COE或执行上下文。实际上,他使用一个Web服务器示例来说明他的观点here(强烈建议阅读)。

全额归功于linux kernel documentation

答案 14 :(得分:0)

多线程适用于受虐狂。 :)

如果您担心不断创建线程/分叉的环境,例如处理请求的网络服务器,您可以预分叉进程,如有必要,可以预分数百个进程。由于它们是 Copy on Write 并在写入发生之前使用相同的内存,因此速度非常快。它们都可以阻塞,侦听同一个套接字,第一个接受传入 TCP 连接的可以使用它运行。使用 g++,您还可以将函数和变量分配到紧密放置在内存中(热段),以确保何时写入内存,并导致整个页面被复制,至少后续写入活动将发生在同一页面上。您确实必须使用分析器来验证此类内容,但如果您担心性能,则无论如何都应该这样做。

由于共享对象上的微妙交互、您没有想到的线程“陷阱”,线程应用程序的开发时间要长 3 到 10 倍,并且很难调试,因为您无法随意重现线程交互问题。您可能必须执行各种性能终止检查,例如在每个函数之前和之后检查所有类中的不变量,如果出现问题,您将停止进程并加载调试器。大多数情况下,生产过程中发生令人尴尬的崩溃,您必须仔细研究核心转储,试图找出哪些线程做了什么。坦率地说,除非您明确共享某些内容,否则当分叉进程同样快速且隐式线程安全时,这不值得头疼。至少通过显式共享,如果出现线程样式问题,您可以确切地知道在哪里查看。

如果性能那么重要,请添加另一台计算机并平衡负载。对于调试多线程应用程序的开发人员成本,即使是由经验丰富的多线程程序编写的应用程序,您也可能购买 4 块 40 核英特尔主板,每块 64gigs 内存。

话虽这么说,但存在不适合并行处理的非对称情况,例如,您希望前台线程接受用户输入并立即显示按钮按下情况,而无需等待某些笨拙的后端 GUI 跟上。在多处理几何​​上不合适的情况下性感地使用线程。许多类似的东西只是变量或指针。它们不是可以在分叉中共享的“句柄”。你必须使用线程。即使您进行了 fork,您也会共享相同的资源并受到线程样式问题的影响。

答案 15 :(得分:-1)

如果你需要共享资源,你真的应该使用线程。

还要考虑这样一个事实,即线程之间的上下文切换比进程之间的上下文切换要便宜得多。

除非你有充分的理由这样做(安全性,经过验证的性能测试等等),否则我认为没有理由明确采用单独的流程。