何时使用clone()和fork而不是pthreads?

19

我在这个领域是初学者。

我已经学习了 fork(), vfork(), clone() 和 pthreads。

我注意到 pthread_create() 会创建一个线程,这比使用 fork() 创建一个新进程的开销要小。此外,该线程将与父进程共享文件描述符、内存等内容。

但是,在什么情况下使用 fork()clone() 比使用 pthreads 更好呢?你能否给我举一个现实世界的例子来解释一下呢?

非常感谢。


4
你正在比较两种不同的事物。没有谁更好,而是要看哪种更适用于哪里。 - Vinayak Garg
好的,如果我们想要分离执行,那么fork()更好,而当我们想要在相同的地址空间上由不同线程执行一些操作时,pthread就很棒了。是这样吗? - Brijesh Valera
2
pthread_create() 调用 clone() - Zaffy
2
pthread_create()的开销比fork()还要大。实际上,它是更多。两者都在内部调用clone系统调用,但pthread用户空间库需要更新许多线程跟踪结构并创建新的内核任务。 - smokku
4个回答

37

clone(2)是一个Linux特有的syscall,主要用于实现线程(特别是用于pthread_create)。使用不同的参数,clone也可以具有类似于fork(2)的行为。很少有人直接使用clone,使用pthread库更加可移植。您可能只有在实现自己的线程库(与Posix-threads竞争)时才需要直接调用clone(2)系统调用 - 这非常棘手(特别是因为锁定可能需要在机器调整的汇编代码例程中使用futex(2)系统调用,请参见futex(7))。您不希望直接使用clonefutex,因为使用pthread更简单。

(在使用pthread_create期间,在libpthread.so内部需要进行一些簿记工作,其他的pthread函数也需要这样)

正如Jonathon所回答的那样,进程有自己的地址空间和文件描述符集。进程可以使用execve系统调用执行新的可执行程序,该系统调用基本上初始化了地址空间、堆栈和寄存器以启动一个新程序(但文件描述符可能会被保留,除非使用close-on-exec标志,例如通过openO_CLOEXEC)。

On Unix-like 系统中,所有 进程(除了 pid 为1的通常是 init 的第一个进程)都由 fork(或其变种如 vfork;你可以使用 clone,但不建议这样做,因为它的行为类似于 fork)创建。

(技术上,在 Linux 上,有一些少数怪异的例外情况,您可以忽略,特别是内核进程或线程以及一些罕见的内核启动的进程,例如 /sbin/hotplug ....)

forkexecve 系统调用是 Unix 进程创建的核心(与 waitpid 和相关系统调用一起使用)。

多线程进程有几个线程(通常是通过pthread_create创建的),它们共享相同的地址空间和文件描述符。当您想要在相同的地址空间中并行处理相同数据时,可以使用线程,但是您应该关注同步和锁定。阅读pthread教程以获取更多信息。

我建议您阅读一本好的Unix编程书籍,如高级Unix编程和/或(免费提供的)高级Linux编程


那么,如果我正在创建一个HTTP Web服务器,它必须响应多个传入请求,使用pthread是否更好? - RoadRunner
没有通用规则。然而,Web服务器确实是多线程方法的一个很好的案例(但也有多进程Web服务器)。还可以查看libonion,这是一个HTTP服务器库。 - Basile Starynkevitch

14
fork(和其他类似工具)的优点和缺点在于,它会创建一个现有进程的克隆新进程。

这是一个缺点,因为正如您指出的那样,创建新进程需要相当多的开销。这也意味着进程间通信必须通过某些“批准”的渠道进行(管道、套接字、文件、共享内存区域等)。

这是一个优点,因为它提供了(更大的)父子进程之间的隔离。例如,如果子进程崩溃,您可以轻松地将其杀死并启动另一个进程。相比之下,如果子线程死亡,则很难将其杀死 - 无法确定该线程独占持有哪些资源,因此无法清理它。同样,由于进程中的所有线程共享一个公共地址空间,因此遇到问题的一个线程可能会覆盖所有其他线程正在使用的数据,因此仅仅杀死一个线程并不一定足以清理混乱。

换句话说,使用线程有一点赌博性质。只要您的代码全部清晰,就可以通过在单个进程中使用多个线程来提高一些效率。使用多个进程会增加一些开销,但可以使您的代码更加强大,因为它限制了单个问题可能导致的损害,并且如果进程确实遇到严重问题,它可以更轻松地关闭和替换。

至于具体的例子,Apache可能是一个相当不错的例子。它将使用多个线程每个进程,但为了在出现问题时限制损害(以及其他事情),它限制每个进程的线程数量,并且还可以/将会同时运行几个单独的进程。在一个不错的服务器上,您可能有8个每个具有8个线程的进程。大量的线程帮助其服务大量客户在主要是I / O绑定的任务中,分解成进程意味着如果出现问题,它不会突然变得完全无响应,并且可以在不失去大量内容的情况下关闭和重新启动进程。


2
创建进程比创建线程的开销更大是一个谬论。在Linux中,它们都是内核任务,使用相同的开销创建(感谢写时复制“魔法”)。使用共享资源进行通信比使用消息传递IPC更有效率也是一个谬论,特别是在现代多核机器上,通信线程可能驻留在不同的CPU芯片上。 - smokku
2
@smoku:不,这不是一个神话。即使使用Copy On Write技术,创建一个新的进程仍然需要复制现有进程的页表,而创建一个线程只需重复使用现有的页表。是的,它减少了需要复制的内存量,但在使用大量内存的进程中,页表仍可能轻松达到几兆字节。 - Jerry Coffin
今天复制几兆字节的内存已经不是问题了,这可不是20世纪80年代了。 - smokku
4
@smoku说过这不是一个问题。但是,你说创建进程的开销比创建线程的开销更大只是一个谬论。如果你想改变立场,认为这种开销通常完全合理,我完全同意。但声称复制数百兆字节的数据与根本不复制相比没有开销,这就是彻头彻尾的谎言。 - Jerry Coffin
好的。我可以同意这种推理。:-) 然而,你的销售方式仍然是如此“80年代”的思维方式,并且不断地宣传选择线程而不是完整进程是一个毫无头脑的选择,因为线程是如此轻量级。另外,一旦你添加了所有pthread的簿记到一个克隆任务中,我不确定fork一个进程是否比pthread_create()还要更多的开销。 :-) - smokku
1
@smoku:我在这里没有出售任何东西。根据我的经验,你可以通过使用线程而不是单独的任务来减少开销(包括生成和通信)。基本上,开发Web服务器的所有人(只是一个显而易见的例子)似乎都同意这一点。同时,我认为我给出了一个相当平衡的观点--这种方法也有缺点。 - Jerry Coffin

6
这些是完全不同的东西。fork()创建一个新的进程pthread_create()创建一个新的线程,该线程在相同进程的上下文中运行。
线程共享相同的虚拟地址空间、内存(好或坏)、一组打开的文件描述符等等。
进程(基本上)是完全独立的,不能相互修改。
你应该阅读这个问题:

举个例子,如果我是您的shell (例如bash),当您输入像ls这样的命令时,我会创建一个新进程,然后exec()执行 ls可执行文件。(然后我wait()等待子进程完成,但这超出了范围。) 这会发生在完全不同的地址空间中,如果ls崩溃了,我也无所谓,因为我仍然在自己的进程中执行。

另一方面,假设我是一个数学程序,并被要求计算两个100x100矩阵的乘积。我们知道矩阵乘法是尴尬并行问题。因此,我将矩阵保存在内存中。我启动N个线程,它们都在同一源矩阵上进行操作,并将其结果放入结果矩阵的适当位置。请记住,这些在线程在同一进程的上下文中运行,因此我需要确保它们不会互相干扰数据。如果N为8且我有一个八核CPU,则可以同时有效地计算矩阵的每个部分。


所以fork()只有在我们希望执行在不影响调用进程的单独空间时才会使用。这是唯一的事情吗? - Brijesh Valera
1
不,fork(以及其变体vfork)是创建进程的唯一方法(除非您使用Linux特定的clone系统调用以完全复制fork的行为)。除了第一个进程init(以及一些奇怪的Linux特定例外情况)之外,所有应用程序进程都是由fork创建的。 - Basile Starynkevitch

0

在Unix系统中使用fork()(及其相关函数)的进程创建机制非常高效。 此外,大多数Unix系统不支持内核级线程,即线程不是内核所认可的实体。因此,在这种系统上,线程无法从内核级别的CPU调度中受益。pthread库实现了这一功能,它并非内核级别,而是某个进程本身。 而在这种系统上,pthread是利用vfork()实现的,作为轻量级进程。 因此,在这种系统上使用线程除了提高可移植性外没有任何意义。

据我了解,Sun Solaris和Windows拥有内核级线程,而Linux家族不支持内核级线程。

通过进程间管道和Unix域套接字进行的IPC非常高效,且不存在同步问题。 希望这能清楚说明何时何地实际上应该使用线程。


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