我在这个领域是初学者。
我已经学习了 fork()
, vfork()
, clone()
和 pthreads。
我注意到 pthread_create()
会创建一个线程,这比使用 fork()
创建一个新进程的开销要小。此外,该线程将与父进程共享文件描述符、内存等内容。
但是,在什么情况下使用 fork()
和 clone()
比使用 pthreads 更好呢?你能否给我举一个现实世界的例子来解释一下呢?
非常感谢。
clone(2)是一个Linux特有的syscall,主要用于实现线程(特别是用于pthread_create
)。使用不同的参数,clone
也可以具有类似于fork(2)的行为。很少有人直接使用clone
,使用pthread库更加可移植。您可能只有在实现自己的线程库(与Posix-threads竞争)时才需要直接调用clone(2)
系统调用 - 这非常棘手(特别是因为锁定可能需要在机器调整的汇编代码例程中使用futex(2)系统调用,请参见futex(7))。您不希望直接使用clone
或futex
,因为使用pthread更简单。
(在使用pthread_create
期间,在libpthread.so
内部需要进行一些簿记工作,其他的pthread函数也需要这样)
正如Jonathon所回答的那样,进程有自己的地址空间和文件描述符集。进程可以使用execve系统调用执行新的可执行程序,该系统调用基本上初始化了地址空间、堆栈和寄存器以启动一个新程序(但文件描述符可能会被保留,除非使用close-on-exec标志,例如通过open的O_CLOEXEC
)。
init
的第一个进程)都由 fork
(或其变种如 vfork
;你可以使用 clone
,但不建议这样做,因为它的行为类似于 fork
)创建。
(技术上,在 Linux 上,有一些少数怪异的例外情况,您可以忽略,特别是内核进程或线程以及一些罕见的内核启动的进程,例如 /sbin/hotplug
....)
fork
和 execve
系统调用是 Unix 进程创建的核心(与 waitpid 和相关系统调用一起使用)。
多线程进程有几个线程(通常是通过pthread_create
创建的),它们共享相同的地址空间和文件描述符。当您想要在相同的地址空间中并行处理相同数据时,可以使用线程,但是您应该关注同步和锁定。阅读pthread教程以获取更多信息。
fork
(和其他类似工具)的优点和缺点在于,它会创建一个现有进程的克隆新进程。
这是一个缺点,因为正如您指出的那样,创建新进程需要相当多的开销。这也意味着进程间通信必须通过某些“批准”的渠道进行(管道、套接字、文件、共享内存区域等)。
这是一个优点,因为它提供了(更大的)父子进程之间的隔离。例如,如果子进程崩溃,您可以轻松地将其杀死并启动另一个进程。相比之下,如果子线程死亡,则很难将其杀死 - 无法确定该线程独占持有哪些资源,因此无法清理它。同样,由于进程中的所有线程共享一个公共地址空间,因此遇到问题的一个线程可能会覆盖所有其他线程正在使用的数据,因此仅仅杀死一个线程并不一定足以清理混乱。
换句话说,使用线程有一点赌博性质。只要您的代码全部清晰,就可以通过在单个进程中使用多个线程来提高一些效率。使用多个进程会增加一些开销,但可以使您的代码更加强大,因为它限制了单个问题可能导致的损害,并且如果进程确实遇到严重问题,它可以更轻松地关闭和替换。
至于具体的例子,Apache可能是一个相当不错的例子。它将使用多个线程每个进程,但为了在出现问题时限制损害(以及其他事情),它限制每个进程的线程数量,并且还可以/将会同时运行几个单独的进程。在一个不错的服务器上,您可能有8个每个具有8个线程的进程。大量的线程帮助其服务大量客户在主要是I / O绑定的任务中,分解成进程意味着如果出现问题,它不会突然变得完全无响应,并且可以在不失去大量内容的情况下关闭和重新启动进程。
fork()
创建一个新的进程。pthread_create()
创建一个新的线程,该线程在相同进程的上下文中运行。举个例子,如果我是您的shell (例如bash
),当您输入像ls
这样的命令时,我会创建一个新进程,然后exec()
执行 ls
可执行文件。(然后我wait()
等待子进程完成,但这超出了范围。) 这会发生在完全不同的地址空间中,如果ls
崩溃了,我也无所谓,因为我仍然在自己的进程中执行。
另一方面,假设我是一个数学程序,并被要求计算两个100x100矩阵的乘积。我们知道矩阵乘法是尴尬并行问题。因此,我将矩阵保存在内存中。我启动N个线程,它们都在同一源矩阵上进行操作,并将其结果放入结果矩阵的适当位置。请记住,这些在线程在同一进程的上下文中运行,因此我需要确保它们不会互相干扰数据。如果N为8且我有一个八核CPU,则可以同时有效地计算矩阵的每个部分。
fork
(以及其变体vfork
)是创建进程的唯一方法(除非您使用Linux特定的clone
系统调用以完全复制fork
的行为)。除了第一个进程init
(以及一些奇怪的Linux特定例外情况)之外,所有应用程序进程都是由fork
创建的。 - Basile Starynkevitch在Unix系统中使用fork()(及其相关函数)的进程创建机制非常高效。 此外,大多数Unix系统不支持内核级线程,即线程不是内核所认可的实体。因此,在这种系统上,线程无法从内核级别的CPU调度中受益。pthread库实现了这一功能,它并非内核级别,而是某个进程本身。 而在这种系统上,pthread是利用vfork()实现的,作为轻量级进程。 因此,在这种系统上使用线程除了提高可移植性外没有任何意义。
据我了解,Sun Solaris和Windows拥有内核级线程,而Linux家族不支持内核级线程。
通过进程间管道和Unix域套接字进行的IPC非常高效,且不存在同步问题。 希望这能清楚说明何时何地实际上应该使用线程。
pthread_create()
调用clone()
。 - Zaffypthread_create()
的开销比fork()
还要大。实际上,它是更多。两者都在内部调用clone
系统调用,但pthread用户空间库需要更新许多线程跟踪结构并创建新的内核任务。 - smokku