分叉 vs 线程化

92

我以前在我的应用程序中使用过线程,并了解其概念,但最近在操作系统课程中我遇到了fork()。它与线程有些相似。

我通过谷歌搜索了解到它们之间的区别:

  1. Fork只是一个看起来与旧进程或父进程完全相同的新进程,但实际上是具有不同进程ID并拥有自己内存的不同进程。
  2. 线程是轻量级进程,具有较少的开销。

但是,我仍然有一些问题。

  1. 何时应该优先选择fork()而不是线程,反之亦然?
  2. 如果我想将外部应用程序作为子进程调用,那么我应该使用fork()还是线程?
  3. 在谷歌搜索时,我发现有些人说在线程内调用fork()是不好的事情。为什么人们想在线程内调用fork(),当它们可以做类似的事情吗?
  4. 是真的吗,fork()不能利用多处理器系统,因为父进程和子进程不会同时运行?
4个回答

89
fork() 和线程方法之间的主要区别在于操作系统架构。在 Unix 设计初期,fork()是一种易于使用且简单的系统,最适合满足大型机和服务器类型的要求,在 Unix 系统上得到了普及。当 Microsoft 从头开始重新设计 NT 内核时,它更专注于线程模型。因此,Unix 系统在处理 fork() 方式方面更有效率,而 Windows 在处理线程方面更有效率。你可以在 Apache 上明显看到这一点,Unix 使用 prefork 策略,Windows 使用线程池。
具体来说:
- 在 Unix 系统上,如果你要执行复杂的任务而不仅仅是实例化一个 worker,或者你想要分离进程的隐式安全沙箱,则应使用 fork()。 - 如果子进程将执行与父进程相同的任务,并使用相同的代码,则使用 fork()。对于较小的子任务,请使用线程。对于独立的外部进程,请不要使用它们,只需使用适当的 API 调用即可。 - 不太确定,但我认为在线程内调用 fork() 的计算成本相当高,需要复制许多子线程和进程。 - 这是错误的,fork() 创建一个新进程,然后利用操作系统任务调度程序中进程可用的所有功能。

3
在Unix系统中,调用外部程序的适当API是先使用fork函数再使用exec函数(这里省略了一些细节)。对于一些特殊情况,有更简单的替代方法(主要是popen函数)。 - ibid
fork() 只会复制调用线程。由于 fork() 使用 COW,因此它并不昂贵。当您想要使用 fork() 但不直接跟随 exec*() 时,使用线程通常是一个坏主意,因为其他线程可能持有无法在子进程中释放的锁,当其他线程突然停止时。 - 12431234123412341234123

50

一个被 fork 的进程被称为重量级进程,而线程被称为轻量级进程。

以下是它们之间的区别:

  1. 被 fork 的进程被认为是子进程,而线程被称为兄弟进程。
  2. 被 fork 的进程与父进程不共享任何资源,如代码、数据、堆栈等,而线程可以共享代码但有自己的堆栈。
  3. 需要操作系统帮助进行进程切换,而线程切换则不需要。
  4. 创建多个进程是一个资源密集型任务,而创建多个线程则是一个资源较少的任务。
  5. 每个进程可以独立运行,而一个线程可以读/写另一个线程的数据。 有关线程和进程的详细信息,请参见讲座 进入图像描述

4
我认为在没有问一些问题的情况下,它是如何获得大量投票的。 1)分叉进程被视为子进程,而线程进程被称为兄弟进程。-- 这一点不相关,机器不关心你是子进程还是兄弟进程。 2)进程切换需要操作系统的帮助,但线程切换则不需要-- 除非您使用称为用户空间线程的东西。 3)创建多个进程是一项资源密集型任务,而创建多个线程则是一项较少资源密集型任务-- 取决于实现方式。 - RaGa__M
1
在Linux和许多其他系统上,进程与进程之间的负担并不显著。由于COW技术,代码以及堆栈的大部分内容都可以在子进程和父进程之间共享。一个派生的进程可以与父进程共享一些东西,例如进程可以请求将来会与子进程共享的内存。 - 12431234123412341234123

22

fork() 生成一个进程的新副本,正如您所指出的一样。上面未提到的是随之而来的 exec() 调用。这将用新的进程(一个新的可执行文件)替换现有进程,并且,fork()/exec() 是从旧进程生成新进程的标准方法。

例如,这就是您的 shell 如何从命令行调用进程的方法。您指定您的进程(比如说 ls),然后 shell 进行 fork 操作,接着再执行 ls

请注意,这与线程操作处于非常不同的层次。线程运行多个在进程内部的执行线路。而 fork 是创建新进程的一种手段。


2
正如@2431234123412341234123所说,在Linux上,由于COW的存在,进程与线程的负担并不重,这取决于它们的使用。COW - 写时复制意味着,只有在forked进程对内存页进行更改时,该进程的内存页才会被复制,否则操作系统将继续将其重定向到父进程的页面。
从编程用例来看,假设在堆内存中有一个大型数据结构,即2d数组[2000000] [100](200 mb),而内核的页面大小约为4 kb。当进程被分叉时,不会为此数组分配新内存。如果更改了特定行(100字节)(无论是在父进程还是在子进程中),则仅会复制并更新forked线程的相应页面(4 kb或8 kb,如果它重叠在两个页面中)。
其他内存部分在forked进程中的工作方式与线程相同(代码相同,寄存器和调用堆栈是分开的)。
正如@Niels Keurentjes所说,在Windows上,从性能角度来看,线程可能更好,但在Linux上,这更多是用例。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接