线程vs Linux中的进程
我最近听到一些人说,在Linux中,使用进程而不是线程几乎总是比较好,因为Linux在处理进程方面非常高效,并且因为与线程相关联的问题太多(如locking)。 但是,我很怀疑,因为在某些情况下线程似乎可以带来相当大的性能提升。
所以我的问题是,当遇到线程和进程可以很好处理的情况时,我应该使用进程还是线程? 例如,如果我正在编写Web服务器,是否应该使用进程或线程(或组合)?
Linux使用1-1线程模型,(对于内核)进程和线程之间没有区别 – 一切都只是一个可运行的任务。 *
在Linux上,系统调用克隆克隆一个任务,具有可configuration的共享级别,其中包括:
-
CLONE_FILES
:共享相同的文件描述符表(而不是创build副本) -
CLONE_PARENT
:不要在新任务和旧任务之间build立父 – 子关系(否则,子的getppid()
=父的getpid()
) -
CLONE_VM
:共享相同的内存空间(而不是创build一个COW副本)
fork()
调用clone(
最less共享)
和pthread_create()
调用clone(
大多数共享)
。 **
由于复制表和为内存创buildCOW映射, fork
成本比pthread_create
成本稍微多一些,但是Linux内核开发人员已经尝试(并成功)将这些成本降到最低。
如果任务之间切换,如果他们共享相同的内存空间和各种表格,将会比不共享时便宜一点,因为数据可能已经被加载到caching中。 然而,即使没有共享,切换任务仍然非常快 – 这是Linux内核开发人员试图确保(并成功确保)的其他内容。
实际上,如果您使用的是多处理器系统, 则不共享对性能的影响实际上可能是有益的:如果每个任务都在不同的处理器上运行,则同步共享内存的成本很高。
*简化。 CLONE_THREAD
会导致信号传递共享(需要共享信号处理程序表的CLONE_SIGHAND
)。
**简化。 同时存在SYS_fork
和SYS_clone
系统调用,但是在内核中, sys_clone
和sys_clone
都是相同的do_fork
函数的非常薄的包装,它本身是一个在copy_process
周围的copy_process
包装。 是的,在Linux内核中,术语process
, thread
和task
可以互换使用。
Linux(甚至是Unix)为你提供了第三种select。
选项1 – 过程
创build一个独立的可执行文件,用于处理应用程序的部分(或全部)部分,并为每个进程分别调用它,例如,程序运行其自身的副本以将任务委托给。
选项2 – 线程
创build一个独立的可执行文件,该文件以单个线程启动,并创build其他线程来执行一些任务
选项3 – 叉
只在Linux / Unix下可用,这有些不同。 分叉进程真的是它自己的进程有自己的地址空间 – 没有什么是孩子可以(通常)影响其父母或兄弟姐妹的地址空间(不像一个线程) – 所以你增加了鲁棒性。
但是,内存页面不会被复制,它们是写入时复制的,因此通常使用的内存通常比您想象的要less。
考虑一个由两个步骤组成的Web服务器程序:
- 读取configuration和运行时数据
- 提供页面请求
如果你使用线程,第1步将完成一次,第2步在多个线程完成。 如果使用“传统”进程,则需要为每个进程重复执行步骤1和2,并将内存用于存储configuration和运行时数据。 如果使用fork(),则可以执行第1步一次,然后执行fork(),将运行时数据和configuration保留在内存中,不会被复制。
所以有三个select。
这取决于很多因素。 进程比线程更重,并具有更高的启动和closures成本。 进程间通信(IPC)也比线程间通信困难和慢。
相反,进程比线程更安全,更安全,因为每个进程都在自己的虚拟地址空间中运行。 如果一个进程崩溃或者有一个缓冲区溢出,它根本不会影响任何其他进程,而如果一个线程崩溃,它就会closures进程中的所有其他线程,并且如果一个线程有一个缓冲区溢出,它将打开所有线程中都有一个安全漏洞。
因此,如果您的应用程序的模块可以通过less量通信独立运行,那么如果您能承担启动和closures成本,则应该使用进程。 IPC的性能受到的影响将是最小的,并且您会对漏洞和安全漏洞稍微安全些。 如果你需要性能的每一点,你可以得到或有大量的共享数据(如复杂的数据结构),去与线程。
对于一些受益于并发的程序,是否使用进程或线程的决定可能是困难的。 以下是一些指导原则,可帮助您确定哪种并发模式最适合您的程序:
1>程序中的所有线程都必须运行相同的可执行文件。 另一方面,subprocess可以通过调用一个exec函数来运行不同的可执行文件。
2>错误的线程可能会损害同一进程中的其他线程,因为线程共享相同的虚拟内存空间和其他资源。 例如,在一个线程中通过未初始化的指针进行的内存写操作会破坏对另一个线程可见的内存。
3>另一方面,错误的进程不能这样做,因为每个进程都有一个程序存储空间的副本。
4>为新进程复制内存相对于创build新线程增加了额外的性能开销。 但是,只有在内存更改时才执行复制,所以如果subprocess只读取内存,则惩罚最小。
5>线程应该用于需要细粒度并行的程序。 例如,如果一个问题可以分解为多个几乎相同的任务,则线程可能是一个不错的select。 程序应该用于需要更粗略的并行性的程序。
6>在线程间共享数据是微不足道的,因为线程共享相同的内存。 (但是,必须非常小心地避免竞争条件,如前所述)。在进程之间共享数据需要使用IPC机制。 这可能会更麻烦,但使多个进程不太可能遭受并发错误。
其他人已经讨论了考虑。
也许最重要的区别是在Windows中进程与线程相比是沉重的和昂贵的,而在Linux中,差别要小得多,所以方程式在不同的地方平衡。
曾经有Unix,在这个很好的Unix中,有很多进程的开销,所以一些聪明的人做的是创build线程,这些线程与父进程共享相同的地址空间,并且只需要一个简化的上下文开关,这将使上下文切换更有效率。
在当代的Linux(2.6.x)中,与线程相比,进程的上下文切换(仅MMU的内容是线程的附加)没有太大差别。 共享地址空间存在问题,这意味着线程中的错误指针可能会破坏父进程的内存或相同地址空间内的另一个线程。
一个进程受到MMU的保护,所以一个错误的指针只会产生一个信号而不会造成损坏。
我通常会使用进程(在Linux中没有太多的上下文切换开销,但是由于MMU的内存保护),但是如果我需要一个实时调度程序类,这是一个不同的茶杯在一起的pthreads。
为什么你认为线程在Linux上有如此大的性能提升? 你有没有这方面的数据,还是只是一个神话?
你的任务有多紧密?
如果他们可以独立生活,那么就使用stream程。 如果他们互相依赖,那就用线程吧。 这样你就可以杀死和重启一个坏的进程而不会干扰其他任务的运行。
我不得不同意你所听到的。 当我们对我们的集群( xhpl
等)进行基准testing时,我们总是通过线程上的进程获得更好的性能。 </anecdote>
线程/进程之间的决定取决于你将要使用它的一点点。 一个过程的好处之一是它有一个PID,可以在不终止父母的情况下被杀死。
对于一个真实世界的Web服务器示例,apache 1.3只用于支持多个进程,但在2.0中,他们添加了一个抽象,以便您可以在两者之间进行切换。 评论 似乎同意进程更健壮,但线程可以提供更好的性能(除了进程的性能很糟糕,你只想使用线程的窗口除外)。
更麻烦的是, 线程本地存储和Unix共享内存是这样的。
线程本地存储允许每个线程有一个单独的全局对象实例。 我唯一一次使用它是在linux / windows上构build仿真环境,在RTOS中运行的应用程序代码。 在RTOS中,每个任务都是具有自己地址空间的进程,在仿真环境中,每个任务都是一个线程(具有共享地址空间)。 通过像单例那样使用TLS,我们可以为每个线程分别创build一个实例,就像在“真正的”RTOS环境下一样。
共享内存可以(显然)为您提供让多个进程访问同一内存的性能优势,但代价是必须正确同步进程。 一种方法是让一个进程在共享内存中创build一个数据结构,然后通过传统的进程间通信(如命名pipe道)向该结构发送句柄。
对于大多数情况下,我宁愿进程超过线程。 当您有一个相对较小的任务(每个分割的任务单元占用的处理开销时间),并且需要在它们之间共享内存时,线程可能非常有用。 想想大阵。 另外(offtopic),请注意,如果您的CPU利用率是100%或接近它,multithreading或处理将不会有任何好处。 (实际上会恶化)
在我最近的LINUX工作中,有一点需要注意的是库。 如果你正在使用线程,确保跨线程使用的任何库都是线程安全的。 这使我烧了几次。 值得注意的是,libxml2不是线程安全的。 它可以用线程安全编译,但这不是你用aptitude install得到的。
如果你需要共享资源,你真的应该使用线程。
还要考虑到线程之间的上下文切换比进程之间的上下文切换便宜得多。
除非你有充分的理由这样做(安全性,经过validation的性能testing等),否则我认为没有理由明确地采取单独的stream程。