fork 和 thread 有什么区别?

106

有人能解释一下分支和线程的区别吗?

4个回答

106

fork会为你创建一个全新的进程,这个进程是当前进程的副本,具有相同的代码段。随着内存镜像的变化(通常是由于两个进程的不同行为),你将获得内存镜像的分离(写时复制),但可执行代码仍然相同。任务之间不共享内存,除非它们使用一些跨进程通信(IPC)原语。

一个进程可以拥有多个线程,每个线程在同一个进程上下文中并行执行。内存和其它资源在线程之间共享,因此共享数据必须通过某些原语和同步对象(如互斥对象条件变量信号量)来避免数据损坏。


3
您可能希望将“当前进程的副本”称为子进程。 - user1831086
1
文本段通常是共享的(虚拟的),甚至数据段也可以进行写时复制。 - Jé Queue
把其他线程分叉时会发生什么? - Michael

92

分支

分支实际上是一个新的进程,它看起来与旧的或父进程完全相同,但它仍然是具有不同进程ID和自己内存的不同进程。父进程为子进程创建了一个单独的地址空间。父进程和子进程都拥有相同的代码段,但彼此独立执行。

分叉的最简单示例是在Unix/Linux中在shell上运行命令。每次用户发出命令时,shell就会分叉出一个子进程来完成任务。

当发出分叉系统调用时,会创建父进程所有页面的副本,并由操作系统将其加载到子进程的单独内存位置中,但在某些情况下,这并不是必要的。例如,在“exec”系统调用中,无需复制父进程的页面,因为execv会替换父进程本身的地址空间。

关于分叉需要注意的几点是:

  • 子进程将拥有自己独特的进程ID。
  • 子进程将拥有父进程文件描述符的副本。
  • 父进程设置的文件锁将不会被子进程继承。
  • 父进程中打开的任何信号量在子进程中也将打开。
  • 子进程将拥有其自己的父进程消息队列描述符的副本。
  • 子进程将拥有自己的地址空间和内存。

线程

线程是轻量级进程(LWP)。传统上,线程只是一个包含CPU状态和一些其他最小状态的过程,而过程则包含其余内容(数据、堆栈、I/O、信号)。线程比“分叉”或生成新进程需要更少的开销,因为系统不会为进程初始化新的系统虚拟内存空间和环境。在多处理器系统上表现最佳,因为进程流可以安排在另一个处理器上运行,从而通过并行或分布式处理获得速度提升,但在利用I/O和其他可能阻止进程执行的系统函数的延迟的单处理器系统上也可以发现收益。

同一进程中的线程共享:

  • 进程指令
  • 大部分数据
  • 打开文件(描述符)
  • 信号和信号处理程序
  • 当前工作目录
  • 用户和组ID
  • 更多细节可以在这里找到。


    3
    一个进程可以有多个线程。如果进程中的某个线程调用fork,那么新分叉出的进程是否完全复制了内存,但只有调用fork的线程在新的进程中? - Michael
    当一个线程分叉时,其他线程会继续在原始进程中运行。新的进程是原始进程的副本,其中包括所有线程和它们的状态。 - Michael
    尽管子进程有自己的地址空间,但它是父进程地址空间的精确副本,这意味着如果在父进程中有一个随机变量 int a = 10 占用了父进程地址空间中的第1000个内存位置,那么即使在子进程中 int a = 10 也将占用相同的第1000个内存地址。然而,对 a 的修改对于父进程和子进程是不同的,它们每个都可以修改 a 而不影响其他进程,因为它是一个副本。 - iCantC

    35

    Dacav的回答很好,我想补充一点,不是所有线程模型都能提供真正的多进程。

    例如,Ruby的默认线程实现并不使用真正的操作系统/内核线程。相反,它通过在单个内核线程/进程中切换Thread对象来模拟具有多个线程的效果。

    这在多处理器/多核系统上非常重要,因为这些轻量级线程只能在单个核心上运行-您无法从多个线程中获得太多性能提升。

    另一个地方,当一个线程阻塞时(等待I/O或调用驱动程序的IOCTL),所有线程都会阻塞。

    这种情况现在已经不太常见了-大多数线程实现使用内核线程,它们不会受到这些问题的影响-但是为了完整起见,值得一提。

    相比之下,fork会给您另一个进程,该进程可以同时在另一个物理CPU上运行,而原始进程正在执行。有些人发现IPC更适合他们的应用程序,而其他人则更喜欢线程。

    祝你好运,玩得开心!多线程既具有挑战性又具有回报。


    8
    对于 "not all threading give you true multiprocessing" 这句话,如果得到一个 "+1 for hitting a nerve" 的反应,意味着这个话题引起了共鸣。简单翻译一下,它的意思是并非所有线程都能提供真正的多进程处理能力。 - Dacav

    9

    线程是并行运行的函数,而fork则是具有父进程继承关系的新进程。 线程适用于并行执行任务,而fork则是同时运行的独立进程。 线程存在竞争条件,需要使用信号量、锁或互斥体进行控制;管道可以同时在fork和线程中使用。


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