本系列文章讨论Perl的三个关键功能:进程(Processes)管道(Pipes)信号(Signals)。通过建立一个新进程,Perl程序可以运行另一个程序甚至是它自己的拷贝。管道允许Perl脚本刚其它进行交换数据,而信号使Perl脚本监视和控制其它进程成为可能。本文讨论的是其中的:进程。

进程(Processes


UNIX、VMS、Windows NT/2000以及其它现代操作系统都是多任务的。它们能同时运行多个程序,每个程序运行在独立的线程运行,称为进程。在多核的电脑中,进程实际上是运行在不同的CPU上同时运行。而在单核的电脑中,操作系统在多个进程中快速切换,每次执行一小部分,使进程看上去是同时运行的,

网络应用程序通过需要同时做两个或两个以上的事情。例如,服务器通常需要立刻处理客户端的请求,与此同时,还需要监视新的请求。多任务大大简化了程序开发,因为它允许你为应用程序所有操作启动新进程。几乎每个进程都是独立的,允许一个进程继续它的工作而不需要担心它是干什么的。

Perl支持两种多任务。一种基于传统UNIX多重处理(multiprocessing)模式,允许当前进程调用fork()函数克隆它自己。fork()执行后,将会有两个各方面几乎一致的进程。一个进程执行一个任务,另一个执行另一个任务。

另一种基于更现代的理念“线程(thread)”,将所有任务放在一个进程里面。无论如何,单个程序可以有多个线程,它们是相互独立的。

本文将介绍fork()以及与进程有关的变量和函数。我们将在以后的文章里讨论多线程(multithreading )。

fork()函数


fork()函数在所有的UNIX版本的Perl里有效,以及VMS和OS/2。Perl 5.6(及更高版本的Perl)支持Microsoft Windows平台的fork()函数,可惜的是,不支持Macintosh。

Perl的fork()函数没有参数并返回一个数字作为结果。当fork()被调用的时候,它生成一个进程的精确副本。这个副本称为子进程(child),共享当前的值和变量,文件句柄(包含标准I/O缓存的数据)以及其它数据结构。事实上,调用fork()的副本进程都有一样的内存。就好像科幻电影里演的一个人无意中走进克隆出来的小房间里。

要保证和平共处,必须清楚哪个是父进程哪个是子进程。系统里所有的进程都有一个唯一的正整数,称为进程ID(process ID)或PID。

调用fork()函数之后,父进程和子进程判断这个函数的返回值。在父进程里,fork()返回子进程的PID。在子进程里,fork()返回数字0。根据这个返回值来判断是否为父进程。

$pid = fork()

创建一个新进程。在父进程中返回子进程的PID,在子进程中返回0。如果发生错误(比如,内存不足)返回undef,并将$!设为对应的错误信息


如果父进程和子进程想与其它进程通讯,可借助管道(pipe)或者通过共用存储器(shared memory)。父进程和子进程可以发送各自的PID给kill()函数。父进程通过fork()函数的返回值来获取子程序的PID,子进程可以通过调用getppid()函数来获得父进程的PID。进程可以通过特殊的变量$$来获得自己的PID。

$pid = getppid()

返回父进程的PID。每个Perl都有一个父进程,即使它通过命令行直接启动(此时,它的父进程是shell进程)。
$$

$$变量存储着当前进程的PID。它是只读的。


kill()函数将在后面的“信号”一节进行讨论。

正如你希望的那样,一个子进程也可以有它自己的 fork(),创建一个孙进程。原来的父进程可以再次 fork(),当然它的子进程和孙进程也一样可以。这样,Perl脚本可以创建一批完整(而友好的,希望如此)的进程。除非明确操作,这批进程属于同一个进程组(process group)。

每个进程组有一个唯一的ID,通常与共同的祖先(第一个使用 fork()的进程)的PID相同。这个值可以通过 getpgrp()来获得:

$processid = getpgrp([$pid])

如果指定了$pid, getpgrp() 函数返回对应的组ID。如果没有指定PID,返回当前进程所在的进程组的ID。


进程组中的每个成员共享所有从父进程创建时,拥有的文件句柄。特别是,共享 STDINSTDOUT。可以在任何子进程中修改关闭文件句柄,或者重新打开它。系统一直保持跟踪:子进程打开文件句柄,但没有关闭文件。直到最后的子进程时,关闭该文件句柄的拷贝。

#!/usr/bin/perl
#fork.pl - 创建单个的子进程
##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict;
use warnings;

print "PID = $$\n";

my $child = fork();
die "无法创建子进程:$!" unless defined $child;

if($child > 0) { #父进程
print "父进程:PID = $$, 子进程 = $child\n";
} else { #子进程
my $ppid = getppid();
print "子进程:PID = $$, 父进程 = $ppid\n";
}


通过检查 $child 来确定,我们的程序是以父进程运行还是子进程。如果 $child 非零,我们进入父进程处理:我们打印出了我们的PID和 $child 的内容,以及子进程的PID。

如果 $child 是零,那我们运行在子进程里。我们通过调用 getppid()来获取父进程的PID,并将其输出。

该代码的执行结果:
% fork.pl
PID=372
父进程: PID=372, 子进程=373
子进程: PID=373, 父进程=372

注意,在Windows平台运行此脚本,将会出现类似错误:
The getppid function is unimplemented at fork.pl line 18.

原因在于,Windows平台的Perl并没有实现 getppid()函数,请使用UNIX/Linux测试本代码。


system()exec()函数


在Perl启动子进程的另一种方法是 system()system()将另一个程序作为子进程运行,等待其完成,并返回结果。如果成功, system()返回0(注意,这和一般的Perl习惯不同。【译注:一般地,在Perl(及其它类C语言)里,0表示否定;非0表示肯定】)。如果子程序没有启动,返回-1。或者返回其它错误代码。有关错误代码的完整解释,请通过perlvar查看$?变量的详细介绍。


$status = system('command and arguments')

$status = system('command', 'and', 'arguments')

system()函数将命令(command)作为一个子进程来执行,并等待它退出。该命令和它的参数(arguments)可以在单个字符串里指定,也可以将它们作为列表的元素来指定。在第一种方式里,字符串将完整无缺地传递给shell(【译注:UNIX/Linux的shell;Windows平台的命令提示符(cmd.exe)】)。它允许你执行包含shell元字符(shell metacharacters)(如输入/输出,重定向),但可能无意中执行了shell命令。后一种方式允许你执行带有空白字符分隔的参数,shell元字符和其它特殊字符,但不解析元字符。

exec()函数跟 system()很像,但替换当前进程为指定的命令。如果成功,没有任何返回值,因为进程已经结束。新进行将拥有与旧的那个同样的PID,并共享相同的 STDINSTDOUTSTDERR文件句柄。无论如何,其它已打开的文件句柄将自动关闭。(你可以通过改变 $~F 变量的值来让 exec()保持已打开的文件句柄。详细内容请查看perlvar文档。)


$status = exec('command and arguments')

$status = exec('command', 'and', 'arguments')

exec()执行一个命令,并替换当前进程。只有出现错误的时候,它才返回状态代码。融不会返回任何结果。单值形式和列表形式与 system() 的意思一样。


exec()通常与将一个命令作为子进程运行,处理完一些指定的步骤的fork()配合使用。例子,在上面fork.pl的代码片段后面,子进程在一个文件上重新打开 STDOUT,然后调用 exec() 来运行 ls -l 命令。在UNIX系统上,这个命令获取一个很长的列表。其效果是,在后台运行 ls -l 命令,并将其输出写到指定的文件中。代码如下:
my $child = fork();
die "Can't fork: $!" unless defined $child;
if ($child == 0) { # we are in the child now
open (STDOUT,">log.txt") or die "open() error: $!";
exec ('ls','-l');
die "exec error(): $!"; # shouldn't get here
}


知识共享许可协议
本作品采用知识共享署名 3.0 Unported许可协议进行许可。

上一篇 下一篇