Perl

Perl的进程控制(二)

在实际的编程中,启动一个子进程,然后等待其结束,然后父进程接着干别的,这是很理想的情况。经常地,我们会需要一个Timeout的机制,在子进程运行的时间超出了我们的期望,明显情况不对的时候能够及时地终止它。
好消息是Perl提供了类似于Unix的信号机制,我们可以在某个信号上注册一个对应该信号的处理逻辑,比如收到ALARM信号的时候就强制退出(杀死)子进程。而父进程通过调用alarm函数使得在指定的时间会有一个SIGALRM信号发送过来,这样就能够通过(控制发送ALARM信号+ALARM信号处理函数)实现一个定时的机制。
当然,父进程还是通过waitpid以等待子进程的正常结束,ALARM信号只是用于处理Timeout的情况。所以waitpid之后,要调用“alarm 0”来取消之前的Timeout定时器。
一个典型的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#!/usr/bin/perl
use strict;
use POSIX ":sys_wait_h";
my $timeOut = 5;
my $cmd = 'ping 10.10.10.10';
my $rv = 0;
my $child_pid = open ( my $CMD, "-|", $cmd ) // return -1;
if ( $child_pid ) {
# parent process
eval {
# Handle the SIGALRM and kill the child process(es)
local $SIG{ALRM} = sub {
kill 'SIGTERM', $child_pid;
sleep 1; # Give the process time to shut down nicely, then murder it.
kill 'SIGKILL', $child_pid;
die "timeout\n";
};
#-- Start Timer --------------------------------------------------------------#
alarm $timeOut if $timeOut;
# Wait for child processes. SIGALRM should take care of any hangs.
waitpid($child_pid, 0); # $? is set by waitpid when the process exits.
$rv = $?;
alarm 0 if $timeOut;
# -- Time's up! --------------------------------------------------------------#
};
if ( $@ ) {
#Exception handle
print "Error: $@\n";
$rv = -1;
}
if ($rv != 0) {
print "Failed to issue the command:" . "$rv" . "\n";
} else {
print "Succeed to issue the command:" . "$rv" . "\n";
}
}
close $CMD;

上面的代码通过一个ping命令来模拟一个运行超时的命令。第10行的open函数用于异步启动一个子进程执行该命令。16~21行的代码实现了针对SIGALRM信号的处理,24行启动了Alarm机制的timer,28行在非timeout的情况(子进程正常退出的情况)下取消timer。
注意eval经常被用于这种情况去捕获一个代码block中可能的异常。

Perl的进程控制(一)

先简单总结一下,Perl里调用shell命令的时候,会用到进程控制。经常用到的有以下几个方式:

  1. 通过system() 调用shell命令,Perl是父进程,Shell本身是子进程,实际执行的命令本身是孙子进程。
    例子:
1
system "ls -l \$HOME";

注意双引号内变量内插需要转义符。

  1. 通过exec()调用shell命令。1.与2.的区别在于system会等待shell返回,而exec直接结束Perl进程,要执行的命令在Shell中执行完毕后退出。
    例子:
1
exec "date";
  1. 反引号··用于捕获命令输出,不能乱用。通常返回的内容带回车符,依照需要可能要使用chomp。Perl解释反引号里的值的方式类似于system的单参数形式,在解释器中会以双引号字符串形式展开,这意味着反斜线转义与变量内插都会正常处理。
    例子:
1
2
my $now = `date`;
print "The time is now $now";
  1. 以上都是同步处理子进程。 使用open连接至文件句柄的方式可以启动并发运行的子进程。注意管道符号的位置,它表示文件句柄是被管道到命令的标准输出还是标准输入。
    例子:
1
my $child_pid = open ( my $CMD, "-|", @cmd_array ) or die "Failed to create a child process";

在上面的例子里, 一个文件句柄$CMD被打开并通过open被连接,@cmd_array表示的一个命令(包括其options)被异步地执行,其输出则管道至$CMD。

  1. 再高级一点儿的话,Perl也可以使用类似系统调用的fork()。和Unix下的fork()系统调用是一样一样的。
    例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
my $cld_pid = fork();# fork出一个子进程
if (!defined $cld_pid) {
die "Failed to fork a process.\n";
}
unless ($cld_pid) {
# 子进程
print "child!\n";
sleep 3;
} else {
# 父进程
print "parent!\n";
waitpid($cld_pid, 0);
}
# 父进程子进程都会执行到的地方
print "common.\n";

此例子的输出是:

1
2
3
4
parent
child!
common.
common.

注意父进程在打印第11行后会等待子进程结束,然后才执行15行。
实际上,system 做的事情,就是fork一个子进程处理命令,在fork出的子进程内调用exec执行命令(对应替换我们上面例子里的第8行),然后父进程wait在子进程的pid上。因为是exec调用,所以子进程就结束了, 等候在其上的父进程也就结束退出。—— 这是Unix系统标准的系统调用方式。