【问题标题】:When is clone() and fork better than pthreads?什么时候 clone() 和 fork 比 pthreads 更好?
【发布时间】:2012-07-24 15:43:11
【问题描述】:

我是这方面的初学者。

我研究过fork()vfork()clone() 和 pthreads。

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

但是fork()clone() 什么时候比 pthread 更好?你能举个真实的例子给我解释一下吗?

提前致谢。

【问题讨论】:

  • 你在比较两个不同的东西。没有一个更好。正好,哪里都适用。
  • Ohkk.. Gottaa 我们想要单独执行,然后 fork() 更好,当我们想要由不同线程在相同地址空间上执行一些操作时,pthread 很酷。是这样吗?
  • pthread_create() 致电clone()
  • pthread_create() 的开销低于fork()。事实上它是更多。两者都在内部调用 clone syscall,但 pthread 用户空间库需要更新用户空间中的许多线程跟踪结构,以创建新的内核任务。

标签: linux pthreads clone fork


【解决方案1】:

clone(2) 是 Linux 特定的syscall,主要用于实现线程(特别是用于pthread_create)。使用各种参数,clone 也可以有类似fork(2) 的行为。很少有人直接使用clone,使用pthread库更便携。只有在实现自己的线程库(Posix 线程的竞争对手)时,您才可能需要直接调用 clone(2) 系统调用,这非常很棘手(特别是因为锁定可能需要使用 futex(2)机器调整的汇编编码例程中的系统调用,请参阅futex(7))。您不想直接使用clonefutex,因为pthread 使用起来要简单得多。

(其他 pthread 函数需要在 clone 之后在 pthread_create 期间在 libpthread.so 内部完成一些簿记)

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

在类 Unix 系统上,所有进程(除了第一个进程,通常是 pid 1 的init)由fork(或类似vfork 的变体;你可以,但不想使用clone,因为它的行为类似于fork)。

(从技术上讲,在 Linux 上,您可以忽略一些奇怪的异常,特别是内核进程或线程以及一些罕见的内核启动的进程启动,例如 /sbin/hotplug ....)

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

一个多线程进程有多个线程(通常由pthread_create 创建)共享相同的地址空间和文件描述符。当您希望在同一地址空间内并行处理同一数据时,您可以使用线程,但是您应该关心同步和锁定。阅读pthread tutorial 了解更多信息。

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

【讨论】:

  • 那么如果我正在创建一个必须响应多个传入请求的 HTTP Web 服务器,是否最好使用 pthreads 代替?
  • 没有一般规则。但是,Web 服务器确实是多线程方法的一个很好的例子(但也有多进程 Web 服务器)。还可以查看libonion,一个 HTTP 服务器库
【解决方案2】:

unix 上使用 fork() (和家族)的进程创建机制非常有效。 此外,大多数 unix 系统不支持内核级线程,即线程不是内核识别的实体。因此,此类系统上的线程无法从内核级别的 CPU 调度中受益。 pthread 库所做的不是 kerenl 而是某些进程本身。 同样在这样的系统上,pthreads 是使用 vfork() 实现的,并且仅作为轻量级进程。 所以在这样的系统上除了可移植性之外,使用线程没有任何意义。

据我了解,Sun-solaris 和 windows 有内核级线程,而 linux 系列不支持内核线程。

使用进程管道和 unix 域套接字是非常高效的 IPC,没有同步问题。 我希望它能明确为什么以及何时应该实际使用线程。

【讨论】:

    【解决方案3】:

    fork(和公司)的优势和劣势在于他们创建了一个克隆现有流程的新流程。

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

    这是一种优势,因为它在父母和孩子之间提供了(很多)更大的隔离。例如,如果一个子进程崩溃,您可以将其杀死并相当容易地启动另一个。相比之下,如果一个子线程死了,杀死它充其量是有问题的——不可能确定该线程专门拥有哪些资源,因此您无法在它之后进行清理。同样,由于进程中的所有线程共享一个公共地址空间,遇到问题的一个线程可能会覆盖所有其他线程正在使用的数据,因此仅仅杀死一个线程不一定足以清理混乱.

    换句话说,使用线程有点像赌博。只要您的代码都是干净的,您就可以通过在单个进程中使用多个线程来获得一些效率。使用多个进程会增加一些开销,但可以使您的代码更加健壮,因为它限制了单个问题可能造成的损害,并且可以在进程遇到重大问题时轻松关闭和替换进程问题。

    就具体示例而言,Apache 可能是一个相当不错的例子。它将在每个进程中使用多个线程,但为了限制出现问题时的损害(除其他事项外),它限制了每个进程的线程数,并且可以/将生成多个同时运行的单独进程。例如,在一台体面的服务器上,您可能有 8 个进程,每个进程有 8 个线程。大量线程帮助它在一个主要 I/O 绑定的任务中为大量客户端提供服务,并将其分解为进程意味着如果确实出现问题,它不会突然变得完全无响应,并且可以关闭并重新启动一个进程而不会损失很多。

    【讨论】:

    • 创建进程比创建线程的开销更大是一个神话。在 Linux 上,两者都是内部的内核任务,以相同的开销创建(感谢写时复制“魔术”)。使用共享资源进行通信比使用消息 IPC 更有效也是一个神话——尤其是在现代多核机器上,其中通信线程可能驻留在不同的 CPU 芯片上。
    • @smoku:不,这不是神话。即使使用 COW,创建新进程也需要复制现有进程的页表,而创建线程只是重用现有页表。是的,它将需要复制的内存量减少了几个数量级,但是在使用大量内存的进程上,页表仍然很容易达到几兆字节。
    • 即使复制几兆字节的内存在今天也不是问题。现在已经不是 1980 年代了。
    • @smoku:没有人说这是个问题。但是,您说创建进程比创建线程具有更多开销是一个神话。如果你想改变你的立场,声称开销通常是完全合理的,我完全同意——但声称复制兆字节的数据与复制那些兆字节的数据相比没有任何开销一切都只是彻头彻尾的谎言。
    • @smoku:我不在这里卖任何东西。我会说,根据我的经验,您可以通过使用线程而不是一直使用单独的任务来减少开销(包括生成和通信)。基本上每个开发 Web 服务器的人(仅举一个明显的例子)似乎都同意。同时,我认为我给出了一个相当平衡的观点——这种方法也有缺点。
    【解决方案4】:

    这些是完全不同的东西。 fork() 创建一个新的processpthread_create() 创建一个新线程,在同一个进程的上下文中运行。

    线程共享相同的虚拟地址空间、内存(无论好坏)、打开的文件描述符集等。

    进程(基本上)彼此完全分离,不能相互修改。

    你应该阅读这个问题:


    举个例子,如果我是你的shell(例如bash),当你输入像ls这样的命令时,我会去fork()一个新进程,然后exec()@987654330 @可执行文件。 (然后我在子进程上wait(),但这超出了范围。)这发生在完全不同的地址空间中,如果ls 爆炸了,我不在乎,因为我仍在执行我的自己的进程。

    另一方面,假设我是一个数学程序,我被要求将两个 100x100 矩阵相乘。我们知道矩阵乘法是一个Embarrassingly Parallel 问题。所以,我有记忆中的矩阵。我产生了 N 个线程,每个线程都对相同的源矩阵进行操作,将它们的结果放在结果矩阵中的适当位置。请记住,它们是在同一进程的上下文中运行的,因此我需要确保它们不会在彼此的数据上打上烙印。如果 N 为 8,并且我有一个八核 CPU,我可以有效地同时计算矩阵的每个部分。

    【讨论】:

    • 所以 fork() 仅在我们希望执行必须在不会影响调用进程的单独空间上进行时使用。这是唯一的吗?
    • 不,fork(及其变体vfork)是唯一的创建进程的方法(除非你在这种情况下使用Linux特定的clone系统调用它完全复制fork 的行为的方式)。所有应用程序进程都是由fork创建的,除了第一个进程init(以及一些奇怪的Linux特定的例外)。
    猜你喜欢
    • 1970-01-01
    • 2011-09-27
    • 1970-01-01
    • 2016-12-10
    • 1970-01-01
    • 2012-05-29
    • 2011-05-12
    • 2010-12-08
    • 2011-05-30
    相关资源
    最近更新 更多