代码之家  ›  专栏  ›  技术社区  ›  devoured elysium

如何在UNIX中使用fork()?为什么不使用fork(pointerToFunctionToRun)形式?

  •  8
  • devoured elysium  · 技术社区  · 14 年前

    我很难理解如何使用Unix fork() . 当需要并行化时,我习惯于在应用程序中分离线程。它总是某种形式的

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

    现在,当学习Unix时 Frk() ,我获得了以下形式的示例:

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

    其中fork后面的代码是“拆分”的。我不明白fork()如何有用。为什么fork()没有与上面的createwthread()相似的语法,在这里您将要运行的函数的地址传递给它?

    要完成类似于createnewthread()的任务,我必须具有创造性,并做一些类似的事情

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

    也许问题是我已经习惯了以.NET的方式生成线程,以至于我无法清楚地思考这个问题。我这里缺什么?有什么好处 Frk() 结束 CreateNewThread() ?

    附言:我知道 Frk() 会产生新的 过程 ,而 创建新线程() 会产生新的 线 .

    谢谢

    8 回复  |  直到 14 年前
        1
  •  9
  •   Jeremy W. Sherman    14 年前

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

    在之后的子进程中调用什么是安全的有很多限制 Frk() (见下文)。期望是 Frk() 调用是生成一个新进程的一部分,该进程运行一个具有自己状态的新可执行文件。此过程的第二部分是调用 execve() 或者它的一个变体,它指定要加载到当前运行的进程中的可执行文件的路径、要提供给该进程的参数以及围绕该进程的环境变量。(没有什么可以阻止您重新执行当前正在运行的可执行文件,并提供一个标志,使其能够在父级停止的位置(如果这是您真正想要的话)获取数据。)

    Unix系统 fork()-exec() 舞蹈大致相当于窗户 CreateProcess() . 一个新的函数更像它: posix_spawn() .

    作为一个应用实例 Frk() ,考虑一个外壳,例如 bash . Frk() 始终由命令shell使用。当您告诉shell运行程序(例如 echo "hello world" ,然后执行该程序。管道是具有 stdout stdin 由父母适当装配 Frk() exec() .

    如果您想创建一个新的线程,您应该使用POSIX线程库。使用创建新的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*/);
    

    在线程可用之前, Frk() 是Unix提供的最接近多线程的东西。现在线程可用,使用 Frk() 几乎完全局限于生成一个新进程来执行不同的可执行文件。

    以下:限制是因为 Frk() 在多线程之前,所以只有调用 Frk() 继续在子进程中执行。每 POSIX :

    应使用单螺纹创建工艺。如果多线程进程调用fork(),则新进程应包含调用线程及其整个地址空间的副本,可能包括互斥体和其他资源的状态。因此,为了避免错误,子进程只能执行异步信号安全操作,直到调用某个exec函数为止。[thr][option start]可以通过pthread-atfork()函数建立分叉处理程序,以便在fork()调用之间保持应用程序不变量。[选项结束]

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

    因为您调用的任何库函数都可能代表您生成了一个线程,偏执的假设是,在调用之间,您总是被限制在子进程中执行异步信号安全操作 Frk() 执行() .

        2
  •  8
  •   Kim Burgaard    14 年前

    撇开历史不谈,在资源所有权和进程和线程之间的生命周期方面存在一些根本性的差异。

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

    另一个区别是fork()的子级可以比其父级长,当进程终止时,所有线程都会死亡。

    在一个客户机-服务器体系结构中,使用fork()而不是创建线程是一个有效的策略来防止内存泄漏,在这种体系结构中,运行时间很长。与其担心清理线程中的内存泄漏,不如派生一个新的子进程来处理每个客户机请求,然后在完成后杀死该子进程。然后,内存泄漏的唯一来源将是发送事件的父进程。

    一个类比:可以将生成线程看作是在单个浏览器窗口中打开选项卡,而分叉就像打开单独的浏览器窗口。

        3
  •  6
  •   Tony Delroy    6 年前

    问为什么更为合理 CreateNewThread 不只是返回线程ID fork() 做。。。毕竟 Frk() 开创先例。你的意见只是因为你先见了一个而被玷污了。退后一步考虑一下 Frk() 复制进程并继续执行…还有什么比下一个指令更好的地方?为什么要通过在协议中添加一个函数调用使事情复杂化(然后只需要 void* )?

    你对迈克的评论是 “我不明白你想在什么环境下使用它。” . 基本上,当你想:

    • 使用exec函数系列运行另一个进程
    • 独立地执行一些并行处理(在内存使用、信号处理、资源、安全性、健壮性方面),例如:
      • 每个进程可能对它们可以管理的文件描述符的数量有干扰性的限制,或者在32位系统上—内存量:第二个进程可以共享工作,同时获得自己的资源
      • Web浏览器倾向于分叉不同的进程,因为它们可以进行一些初始化,然后调用操作系统函数以永久性地降低它们的权限(例如,更改为不受信任的用户ID,更改它们可以访问文件的“根”目录,或使某些内存页为只读);大多数操作系统不允许在每个线程的基础上进行相同程度的细粒度权限设置;另一个好处是,如果子进程SEG错误或类似的错误,则父进程可以处理该错误并继续,而多线程代码中的类似错误会引发关于内存是否崩溃的线程损坏了-或者锁被持有-这样剩下的线程就会受到影响。

    btw/使用unix/linux并不意味着您必须放弃线程 Frk() 正在处理进程…你可以用 pthread_create() 以及相关的函数,如果您更熟悉线程模式的话。

        4
  •  2
  •   Mike Axiak    14 年前

    让生成进程和线程之间的区别留一秒钟:基本上,fork()是一个更基本的原语。虽然spawnewthread必须做一些后台工作才能使程序计数器处于正确的位置,但是fork不做这种工作,它只复制(或实际上复制)您的程序内存并继续计数器。

        5
  •  2
  •   bmargulies    14 年前

    福克和我们在一起已经很久了。在“启动一个线程运行一个特定的函数”的想法出现之前,fork被认为是任何人眼中的闪光。

    人们不使用 fork 因为它是“更好的”,我们使用它是因为它是唯一一个而且也是唯一一个在Linux的所有变体中工作的非特权用户模式进程创建函数。如果要创建 一个过程 ,你必须打电话 . 而且,出于某些目的,过程是您需要的,而不是线程。

    你可以考虑研究关于这个问题的早期论文。

        6
  •  1
  •   dmckee --- ex-moderator kitten    14 年前

    值得注意的是,多重- 处理 和multi不完全一样- 穿线 . fork创建的新进程与旧进程共享的上下文很少,这与线程的情况有很大的不同。

    那么,让我们来看看Unixy 线 系统: pthread_create 语义类似于 CreateNewThread .

    或者,为了扭转它,让我们看看Windows(或者Java或其他线程生存的系统)产生一个与当前运行的进程相同的方法(这是什么) fork 在Unix上是这样的)…好吧,我们可以这样做,除了没有一个:那只是不是所有线程的一部分,所有时间模型。(这不是坏事,注意,只是不同而已)。

        7
  •  1
  •   tchrist    14 年前

    fork 当你想同时做一件以上的事情时。它叫做多任务处理,非常有用。

    例如,这里有一个类似telnetish的程序:

    #!/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;
    

    看看这有多简单?

        8
  •  0
  •   Greg Jaxon    11 年前

    fork()最常用的用途是为连接()s的每个新客户机克隆服务器(因为新进程继承了所有文件描述符,不管它们的状态如何)。 但我还使用它从客户机启动了一个新的(本地运行的)按需服务。 该方案最好使用两个fork()调用来完成——一个在父会话中,直到服务器启动并运行并且能够连接,另一个(我将它从子会话中拨出)成为服务器并离开父会话,这样就不能再通过sigquit(例如)访问它。