Perl多进程与信号量

一、Perl多进程

今天这里就不多说什么是多进程、什么是多线程了,简单来说目的都是充分利用硬件资源,最大化机器性能。
任何语言的新手在遇到多进程编程时,心里往往会发怵,多进程好像很高大上、好复杂的样子,其实不然。我们要区分多进程实现本身和多进程应用编程,多进程本身的实现确实是很复杂的,但是我们更多的是集中在多进程应用编程,这部分就比较简单了。
话不多说,下面先看用perl实现的一个多进程编程例子。

#!/bin/perl
use strict;

# fork returns the child pid to parent, 0 to child or undef if failed to fork.
my $pid = fork();
die "fork failed" unless defined $pid;
if($pid > 0){
  my $i = 1000;
  while($i > 0){
    print "parent process i = $i\n";
    $i--;
  }
}elsif($pid == 0){
  my $i = 1000;
  while($i > 0){
    print "child process i = $i\n";
    $i--;
  }
}

上述例子中,实现多进程最关键的方法就是fork,其功能是将当前代码完全拷贝一份,也就是形成两份相同的程序:父进程和子进程。其中,在父进程中,fork的返回值是子进程的进程号(大于0的值);而在子进程中,fork返回值是0;fork失败时返回的值为负数。fork失败的情况一般是资源耗尽,我自己在编程时遇到过几次,都是fork太多的进程耗尽了系统资源。
通过以上三个返回值,我们就能确定fork有没有成功,以及成功时谁是父进程、谁是子进程,这样就可以决定在父进程或者子进程中做什么样的操作。
例子中父、子进程都是循环输出1000至1,同时给出父子进程提示信息。
输出结果如下:

parent process i = 36
child process i = 254
parent process i = 35
child process i = 253
parent process i = 34
child process i = 252
parent process i = 33
child process i = 251
parent process i = 32
child process i = 250
parent process i = 31
child process i = 249
parent process i = 30
child process i = 248
parent process i = 29
child process i = 247
parent process i = 28
child process i = 246
parent process i = 27
child process i = 245
parent process i = 26
child process i = 244
parent process i = 25
child process i = 243
parent process i = 24
child process i = 242
child process i = 241
child process i = 240
parent process i = 23
child process i = 239
parent process i = 22
child process i = 238
parent process i = 21
child process i = 237
parent process i = 20
child process i = 236
parent process i = 19
child process i = 235
parent process i = 18
child process i = 234
parent process i = 17
child process i = 233
parent process i = 16
child process i = 232

二、僵尸进程

1、什么是僵尸进程?

简单来讲(我最喜欢简单来讲),子进程完成使命(死了),却没有被父进程回收(收尸),什么作用都没有了,却还暂留在系统中,就像僵尸。进程变为僵尸进程时其所占用的资源都会被回收掉,因此不会造成太大的资源泄露,当然,进程本身的信息(进程号,创建时间等)还是存在的。

2、为什么会产生僵尸进程?如何回收?

一般情况是父进程比子进程提前结束并且没有任何其它进程来回收子进程时就会产生僵尸进程,例如我们在程序中没有指明父进程需要等待子进程全部结束并回收子进程时,子进程就会成为僵尸进程。
避免产生僵尸进程需要用到wait或者waitpid函数,这里我使用的是waitpid($pid,0)函数。该函数的功能是等待$pid进程结束并回收它。
代码如下:

#!/bin/perl
use strict;

# fork returns the child pid to parent, 0 to child or undef if failed to fork.
my $pid = fork();
die "fork failed" unless defined $pid;
if($pid > 0){
  my $i = 1000;
  while($i > 0){
    print "parent process i = $i\n";
    $i--;
  }
  # 等待子进程结束并回收它
  waitpid($pid,0);
}elsif($pid == 0){
  my $i = 1000;
  while($i > 0){
    print "child process i = $i\n";
    $i--;
  }
}

三、信号量(进程同步)

不需要多么仔细的观察就能发现第一个样例程序中,两个进程是并行执行的,这或许正是我们使用进程的目的。但是,有些时候,比如说两个进程都需要使用某个资源,而这个资源不能被两个进程同时使用,因此我们希望这两个进程串行执行。这个时候我们就需要使用锁这种东西来确保进程同步。
在这里,我使用了IPC的信号量,并让信号量资源值为1,当资源值为1时,其作用相当于锁。

# new一个信号量集(信号量数组,下标从0开始),第二个参数1表示该信号量集中只有一个信号量(下标为0),其它值为系统定义参数
our $sem = IPC::Semaphore->new(IPC_PRIVATE, 1, S_IRUSR | S_IWUSR | IPC_CREAT);
# 设定信号量集中下标为0的信号量资源值为1
$sem->setval(0,1);
# 该操作表示P操作,请求一个资源,第一个参数表示信号量下标,中间参数表示请求一个资源,最后一个参数表示如果进程结束则直接释放资源
$sem->op(0,-1,SEM_UNDO);
# 该操作表示V操作,释放一个资源
$sem->op(0,1,SEM_UNDO);
# 移除信号量集,如果不移除,则信号量会一直留在系统中
$sem->remove() if defined $sem;

Linux系统中使用ipcs -a命令可以查看当前系统中所有的信号量及共享内存段等使用。有些时候程序意外终止,信号量未释放,那么就可以使用该命令查看信号量,并使用ipcrm -s semid移除用semid标识的信号。

完整代码如下:

#!/bin/perl
use strict;
use IPC::Semaphore;
use IPC::SysV qw(IPC_PRIVATE S_IRUSR S_IWUSR IPC_CREAT IPC_NOWAIT SEM_UNDO);

our $sem = IPC::Semaphore->new(IPC_PRIVATE, 1, S_IRUSR | S_IWUSR | IPC_CREAT);
$sem->setval(0,1);
# fork returns the child pid to parent, 0 to child or undef if failed to fork.
my $pid = fork();
die "fork failed" unless defined $pid;
if($pid > 0){
  $sem->op(0,-1,SEM_UNDO);
  my $i = 1000;
  while($i > 0){
    print "parent process i = $i\n";
    $i--;
  }
  $sem->op(0,1,SEM_UNDO);
}elsif($pid == 0){
  $sem->op(0,-1,SEM_UNDO);
  my $i = 1000;
  while($i > 0){
    print "child process i = $i\n";
    $i--;
  }
}
$sem->remove() if defined $sem;

输出结果如下,可以看到,两个进程时串行执行的,达到了进程同步的效果。

parent process i = 16
parent process i = 15
parent process i = 14
parent process i = 13
parent process i = 12
parent process i = 11
parent process i = 10
parent process i = 9
parent process i = 8
parent process i = 7
parent process i = 6
parent process i = 5
parent process i = 4
parent process i = 3
parent process i = 2
parent process i = 1
child process i = 1000
child process i = 999
child process i = 998
child process i = 997
child process i = 996
child process i = 995
child process i = 994
child process i = 993
child process i = 992
child process i = 991
child process i = 990
child process i = 989
child process i = 988
child process i = 987
child process i = 986
child process i = 985
child process i = 984
child process i = 983
child process i = 982
child process i = 981
child process i = 980
child process i = 979
上一篇:Oracle中加速索引创建或重建的方法


下一篇:为什么我喜欢用Mac开发