如何在unix中使用fork()? 为什么不是fork的forms(pointerToFunctionToRun)?

我在理解如何使用Unix的fork()时遇到了一些麻烦。 我习惯于在需要并行化时在我的应用程序中产生线程。 这总是一些forms

 CreateNewThread(MyFunctionToRun()); void myFunctionToRun() { ... } 

现在,在了解Unix的fork() ,我给出了这个窗体的例子:

 fork(); printf("%d\n", 123); 

其中fork后面的代码是“分割”的。 我无法理解fork()是如何有用的。 为什么fork()与上面的CreateNewThread()没有类似的语法,在那里你传递一个你想运行的函数的地址?

为了完成类似于CreateNewThread()的工作,我必须要有创意,并且要做类似的事情

 //pseudo code id = fork(); if (id == 0) { //im the child FunctionToRun(); } else { //im the parent wait(); } 

也许问题是,我习惯于以.NET的方式产生线程,我不能清楚地想到这一点。 我在这里错过了什么? fork()CreateNewThread()什么优点?

PS:我知道fork()会产生一个新的进程 ,而CreateNewThread()会产生一个新的线程

谢谢

fork()表示“将当前进程状态复制到一个新进程并从这里开始运行”。 因为代码在两个进程中运行,实际上它会返回两次:一次在父进程(返回子进程的进程标识符),一次在子进程(返回零)。

fork()之后调用子进程的安全性有很多限制(见下文)。 期望的是, fork()调用是产生一个运行一个新的可执行文件和自己的状态的新进程的一部分。 这个过程的第二部分是对execve()或其中的一个变体的调用,它指定了要加载到当前正在运行的进程中的可执行文件的路径,要提供给该进程的参数以及围绕处理。 (没有什么可以阻止你重新执行当前正在运行的可执行文件,并提供一个标志,使得它可以在父母离开的地方继续,如果这是你真正想要的。)

UNIX的fork()-exec()跳舞大致相当于Windows的CreateProcess() 。 更新的函数更像这样: posix_spawn()

作为使用fork()一个实际例子,考虑一个shell,比如bashfork()一直由命令shell使用。 当你告诉shell运行一个程序(比如echo "hello world" )时,它自行分叉,然后执行那个程序。 一个流水线是由fork()exec()之间的父级适当地装配stdoutstdin的分叉进程的集合。

如果你想创建一个新的线程,你应该使用Posix线程库。 您使用pthread_create()创建一个新的Posix线程(pthread pthread_create() 。 你的CreateNewThread()例子看起来像这样:

 #include <pthread.h> /* Pthread functions are expected to accept and return void *. */ void *MyFunctionToRun(void *dummy __unused); pthread_t thread; int error = pthread_create(&thread, NULL/*use default thread attributes*/, MyFunctionToRun, (void *)NULL/*argument*/); 

在线程可用之前, fork()是UNIX提供给多线程的最接近的东西。 现在线程可用, fork()用法几乎完全限于产生执行不同可执行文件的新进程。

下面这些限制是因为fork()早于多线程,所以只有调用fork()的线程继续在子进程中执行。 每POSIX :

一个进程应该创建一个单一的线程。 如果多线程进程调用fork(),则新进程将包含调用线程的副本及其整个地址空间,可能包括互斥锁和其他资源的状态。 因此,为了避免错误,子进程可能只执行异步信号安全操作,直到调用其中一个exec函数为止。 [THR] [Option Start]可以通过pthread_atfork()函数建立分叉处理程序,以便在fork()调用中维护应用程序的不变量。 [选项结束]

当应用程序从信号处理程序调用fork()并且由pthread_atfork()注册的任何fork处理程序调用不是异步信号安全的函数时,行为是不确定的。

因为你调用的任何库函数都可能代表你创建了一个线程,所以这个偏执的假设是,你始终被限制在调用fork()exec()之间的子进程中执行异步信号安全的操作。

除历史外,在进程和线程之间的资源所有权和生命时间方面有一些根本性的差异。

当你分叉时,新进程占用一个完全独立的内存空间。 这与创建新线程非常重要。 在多线程应用程序中,您必须考虑如何访问和操作共享资源。 已分叉的进程必须使用共享内存,管道,远程过程调用,信号量等进程间手段来显式共享资源。

另一个区别是fork()的ed子可以超越他们的父母,在这个过程终止的时候所有的线程都会死掉。

在客户端 – 服务器体系结构中,预计会有非常长的正常运行时间,而使用fork()而不是创建线程可能是一个有效的解决内存泄漏的策略。 您不必担心清理线程中的内存泄漏,只需分离新的子进程来处理每个客户端请求,然后在完成后终止该子进程。 内存泄漏的唯一来源将是调度事件的父进程。

一个比喻:你可以将产卵线程想象成在单个浏览器窗口内打开标签,而分叉就像打开单独的浏览器窗口。

问为什么CreateNewThread不只是像fork()那样返回一个线程id呢……在所有fork()设置了先例之后。 你的意见只是在你之前看过一个而已。 退一步,考虑fork()复制过程,并继续执行…比下一个指令更好的地方? 为什么把一个函数调用加入讨价还价(然后一个只用void *)而使事情复杂化?

你对迈克的评论说:“我不明白你想在哪种情况下使用它。” 基本上,当你想要使用它时:

  • 使用exec系列函数运行另一个进程
  • 做一些独立的并行处理(在内存使用,信号处理,资源,安全性方面)
    • 例如,每个进程可能具有对它们可以管理的文件描述符数量的侵入性限制,或者在32位系统上 – 内存量:第二个进程可以共享工作,同时获得自己的资源

顺便说一句,使用UNIX / Linux并不意味着你必须放弃fork()进程的线程…如果你更熟悉线程范例,你可以使用pthread_create()和相关的函数。

让产生一个进程和一个线程放在一边的区别:基本上,fork()是一个更基本的原语。 虽然SpawnNewThread必须做一些后台工作才能使程序计数器在正确的位置,但fork没有这样的工作,它只是复制(或几乎复制)你的程序存储器并继续计数器。

福克已经和我们在一起很长很长的时间了。 在“开始一个运行特定功能的线程”的想法之前,叉子被认为是任何人的眼中闪烁的。

人们不使用fork是因为它更好,因为它是唯一的非特权用户模式进程创建功能,适用于各种Linux。 如果你想创建一个进程 ,你必须调用fork 。 而且,出于某种目的,一个过程就是你所需要的,而不是一个线程。

你可能会考虑研究关于这个主题的早期论文。

值得注意的是多处理与多线程不完全相同。 由fork创建的新进程与旧进程共享的上下文很少,与线程的情况完全不同。

那么,让我们来看看unixy 线程系统: pthread_create具有类似于CreateNewThread语义。

或者,把它转过来,让我们看看窗口(或java或其他系统,它与线程生活)的方式产生一个相同的过程相同,你正在运行(这是什么fork在UNIX上做)..我们可以除了没有一个:那不是全线程的所有时间模型的一部分。 (这不是一件坏事,介意你,只是不同)。

你只要同时想要多个东西就可以fork 。 这就是所谓的多任务处理,而且非常有用。

在这里例如是一个类似telnet的程序:

 #!/usr/bin/perl use strict; use IO::Socket; my ($host, $port, $kidpid, $handle, $line); unless (@ARGV == 2) { die "usage: $0 host port" } ($host, $port) = @ARGV; # create a tcp connection to the specified host and port $handle = IO::Socket::INET->new(Proto => "tcp", PeerAddr => $host, PeerPort => $port) or die "can't connect to port $port on $host: $!"; $handle->autoflush(1); # so output gets there right away print STDERR "[Connected to $host:$port]\n"; # split the program into two processes, identical twins die "can't fork: $!" unless defined($kidpid = fork()); if ($kidpid) { # parent copies the socket to standard output while (defined ($line = <$handle>)) { print STDOUT $line; } kill("TERM" => $kidpid); # send SIGTERM to child } else { # child copies standard input to the socket while (defined ($line = <STDIN>)) { print $handle $line; } } exit; 

看看这是多么容易?

Fork()最流行的用法是为连接()的每个新客户端克隆一个服务器(因为新进程继承了它们存在的所有文件描述符)。 但是我也用它从客户端按需发起一个新的(本地运行的)服务。 这个方案最好用fork()的两个调用来完成 – 一个保持在父会话中,直到服务器启动并运行并且能够连接,另一个(我将它从子节点分离出来)变成服务器并离开父节点所以不能再通过(比如说)SIGQUIT达到。