我是这方面的初学者。
我研究过fork()
,vfork()
和clone()
pthreads 。
我注意到这pthread_create()
将创建一个线程,这比使用fork()
. 此外,线程将与父进程共享文件描述符、内存等。
但是什么时候fork()
比clone()
pthreads 更好?你能通过给出现实世界的例子向我解释一下吗?
提前致谢。
clone(2)是 Linux 特定的系统调用,主要用于实现线程(特别是用于pthread_create
)。使用各种参数,clone
也可以具有类似fork(2)的行为。很少有人直接使用clone
,使用pthread库更便携。clone(2)
只有在实现自己的线程库(Posix 线程的竞争对手)时,您才可能需要直接调用syscall,这非常棘手(特别是因为锁定可能需要在机器调整的汇编编码例程中使用futex(2) syscall ,见futex(7) )。您不想直接使用clone
或futex
因为 pthread 使用起来要简单得多。
libpthread.so
(其他 pthread 函数需要在 a clone
during a之后在内部完成一些簿记pthread_create
)
正如Jonathon回答的那样,进程有自己的地址空间和文件描述符集。并且一个进程可以使用execve系统调用执行一个新的可执行程序,它基本上初始化地址空间、堆栈和寄存器以启动一个新程序(但文件描述符可以保留,除非使用close-on-exec标志,例如O_CLOEXEC
通过打开)。
在类Unix系统上,所有进程(除了init
pid 1的第一个进程,通常是fork
vfork
clone
fork
(从技术上讲,在 Linux 上,您可以忽略一些奇怪的异常,特别是内核进程或线程以及一些罕见的内核启动的进程启动,例如/sbin/hotplug
....)
fork
和execve
系统调用是 Unix 进程创建的核心(带有waitpid和相关的系统调用)。
多线程进程有多个线程(通常由 创建pthread_create
),它们都共享相同的地址空间和文件描述符。当您想在同一地址空间内并行处理相同数据时,您可以使用线程,但是您应该关心同步和锁定。阅读pthread 教程了解更多信息。
我建议您阅读一本好的 Unix 编程书籍,例如Advanced Unix Programming和/或(免费提供)Advanced Linux Programming
(和公司)的优势和劣势fork
在于他们创建了一个新流程,该流程是现有流程的克隆。
这是一个弱点,因为正如您所指出的,创建一个新流程会产生相当多的开销。这也意味着进程之间的通信必须通过一些“批准的”通道(管道、套接字、文件、共享内存区域等)来完成。
这是一种优势,因为它在父母和孩子之间提供了(更多)更大的隔离。例如,如果一个子进程崩溃,您可以将其杀死并相当容易地启动另一个。相比之下,如果一个子线程死了,杀死它充其量是有问题的——不可能确定该线程独占的资源,所以你不能在它之后进行清理。同样,由于进程中的所有线程共享一个公共地址空间,遇到问题的一个线程可能会覆盖所有其他线程正在使用的数据,因此仅仅杀死一个线程不一定足以清理混乱.
换句话说,使用线程有点像赌博。只要您的代码都是干净的,您就可以通过在单个进程中使用多个线程来获得一些效率。使用多个进程会增加一些开销,但可以使您的代码更加健壮,因为它限制了单个问题可能造成的损害,并且可以在进程遇到重大问题时轻松关闭和替换进程问题。
就具体示例而言,Apache 可能是一个相当不错的例子。它将为每个进程使用多个线程,但为了限制出现问题时的损害(除其他外),它限制了每个进程的线程数,并且可以/将产生多个同时运行的单独进程。例如,在一台体面的服务器上,您可能有 8 个进程,每个进程有 8 个线程。大量线程有助于它在一个主要受 I/O 绑定的任务中为大量客户端提供服务,并且将其分解为多个进程意味着如果确实出现问题,它不会突然变得完全无响应,并且可以关闭并重新启动一个过程而不会损失很多。
这些是完全不同的事情。fork()
创建一个新进程。pthread_create()
创建一个新线程,该线程在同一进程的上下文中运行。
线程共享相同的虚拟地址空间、内存(无论好坏)、一组打开的文件描述符等等。
流程(本质上)彼此完全分离,并且不能互相修改。
你应该阅读这个问题:
举个例子,如果我是你的 shell(例如bash
),当你输入一个类似的命令时ls
,我将进入fork()
一个新进程,然后exec()
是ls
可执行文件。(然后我wait()
在子进程上,但这超出了范围。)这发生在完全不同的地址空间中,如果ls
爆炸,我不在乎,因为我仍在自己的进程中执行。
另一方面,假设我是一个数学程序,我被要求将两个 100x100 矩阵相乘。我们知道矩阵乘法是一个令人尴尬的并行问题。所以,我有记忆中的矩阵。我产生了 N 个线程,每个线程都对相同的源矩阵进行操作,将它们的结果放在结果矩阵中的适当位置。请记住,它们是在同一个过程的上下文中运行的,所以我需要确保它们不会在彼此的数据上打上烙印。如果 N 为 8 并且我有一个八核 CPU,我可以有效地同时计算矩阵的每个部分。
使用 fork() (和家族)在 unix 上的进程创建机制非常有效。此外,大多数 unix 系统不支持内核级线程,即线程不是内核识别的实体。因此,此类系统上的线程无法从内核级别的 CPU 调度中受益。pthread 库所做的不是 kerenl 而是某些进程本身。同样在这样的系统上,pthreads 是使用 vfork() 实现的,并且仅作为轻量级进程。因此,除了在此类系统上的可移植性外,使用线程没有任何意义。
据我了解,Sun-solaris 和 windows 具有内核级线程,而 linux 系列不支持内核线程。
使用进程管道和 unix 域套接字是非常有效的 IPC,没有同步问题。我希望它清楚为什么以及何时应该实际使用线程。