设为首页 收藏本站
查看: 1046|回复: 0

[经验分享] perl fork

[复制链接]

尚未签到

发表于 2018-9-1 06:02:25 | 显示全部楼层 |阅读模式
Perl fork()  


  Forking in perl is a nice thing to do, and for some it’s a hard thing  to understand. It can be pretty easy to get lost especially since there  are 100 ways to the same thing. I’m going to attempt to explain a  little bit of the inner workings of fork() in Perl.
  First you have to understand what fork() returns. When you do:

  my $pid = fork();

  If it is the parent, $pid will be assigned the PID of the child.
  
If it is the child, $pid will be assigned 0.
  
If it cannot fork anymore because of no resources, $pid will be  undefined.
  To help show how a fork() program works, I’m going to use this sample  script:

  #!/usr/bin/perl

  my $pid = fork();
  
if (not defined $pid) {
  
print “resources not avilable.\n”;
  
} elsif ($pid == 0) {
  
print “IM THE CHILD\n”;
  
sleep 5;
  
print “IM THE CHILD2\n”;
  
exit(0);
  
} else {
  
print “IM THE PARENT\n”;
  
waitpid($pid,0);
  
}
  
print “HIYA\n”;

  If run, you will see this:

  $ ./f.pl
  
IM THE CHILD
  
IM THE PARENT
  
- sleep for 5 seconds -
  
IM THE CHILD2
  
HIYA

  $ ps -ef | grep fork1.pl
  
derek 6440 2888 0 15:45 pts/2 00:00:00 /usr/bin/perl ./fork1.pl
  
derek 6441 6440 0 15:45 pts/2 00:00:00 /usr/bin/perl ./fork1.pl

  This is a pretty simple script and self explanatory. It starts out  with the fork and then checks the value of $pid through the if  statements and executes the block of code accordingly. What you really  have to understand is that when fork() is called, you now have 2  programs that are the same. So in this example, when we do my $pid =  fork(); you now have 2 processes running. Each process will run the  code. It looks like $pid is only being assigned one value here but it is  actually being assigned two or even three values (undefined if  necessary). When the parent runs checking through the if statements, it  will catch on the last else statement here because $pid is assigned PID  of the child. When the child runs through this block of code, it will  catch on the if ($pid == 0) because the $pid is assigned 0 for a child.  The waitpid() call just waits for the child to exit. If you do not do  this it will become a zombie (defunct) process, which means it has  become detached from it’s parent.
  
So here is exactly what happens when you run this:
  - The program forks, you now have 2 processes, one is the child, one  is the parent.
  
- if (not defined $pid) gets run on both  processes, and die’s if resources aren’t available.
  
- elsif ($pid == 0) gets run on both  processes, if its the child, print “IM THE CHILD”, sleep for 5 seconds  and then print “IM THE CHILD 2″ and exit(0);
  
- While the above statement is running on the child, the parent is going  along with the else {} block of code and prints “IM THE PARENT” and  then waits for the child to exit.
  NOTE: The exit(0) in the child block is very important.. you need the  child to exit its process when it is done, so it will no longer exist.

fork
  首先说说 fork 函数。这个函数用来创 建一个进程,不过创建方法有些不太好理解。 先看下面的程序 fork-test.pl。我是用perl写的,不过相同的功能也可以用  C 来完成。
  

#!/usr/bin/perl  #------------------------------------
  # fork-test.pl
  print "Program started, pid=$$.\n";
  if ($child_pid = fork()) {
  print "I'm parent, my pid=$$, child's pid=$child_pid.\n";
  } else {
  print "I'm child, pid=$$.\n";
  }
  

  运行之后显示下面的结果。
  

Program started, pid=8934.  I'm child, pid=8935.
  I'm parent, my pid=8934, child's pid=8935.
  

  为什么 I'm child 和 I'm parent 都会被显示?这是因为  fork 调用时, 当前的进程会从 fork 的位置一分为二,fork 对两个进程的返回值不同。 在父进程中 fork  返回子进程(即另一个进程)的进程id,而在子进程中 fork 返回 0。 上例的执行过程如下图。

  上例中执行到 Program started 时,只有一个进程  8934,而执行到 fork 时, 进程分为两个,父进程为 8934,子进程为 8935。接下来父进程执行 if 分支, 输入“I'm  parent..”,而子进程执行 else 分支,输出 “I'm child”。

SIGCHLD信号和僵尸进程
  首先说说什么是僵尸进程(zombie  process)。我们知道 Linux 使用进程表来管理进程, 每个进程都在进程表中占据一个位置。当我们用 fork 生成一个子进程,  然后该子进程退出时,系统不会自动回收该子进程所占位置。 此时虽然进程表中有这个子进程的信息,但实际上该子进程早已结束,  于是这个进程就成了“僵尸进程”。
  僵尸进程虽然不占用系统资源,但是它会浪费进程表的位置。如果僵尸进程太多,  有可能会导致不能创建新进程。下面的例子 zombie-test.pl 演示了如何创建僵尸进程:
  

#!/usr/bin/perl  #------------------------------------
  # zombie-test.pl
  sub child {
  print "I'm child, pid=$$.\n";
  }
  while (1) {
  if (fork() == 0) {
  &child;    # 如果当前进程是子进程,则执行 child 函数
  exit;      # 并退出
  } else {
  sleep 5;   # 如果是父进程,则睡眠 5 秒
  }
  }
  

  该程序每隔 5 秒创建一个子进程,子进程输出一行文字后退出。  执行该程序片刻之后,从其他终端用 ps -ef 命令可以看到进程状态。 标有  的就是僵尸进程。
  

  

charlee  11687 10870  0 02:01 pts/1    00:00:00 /usr/bin/perl perl/zombie-test.pl  charlee  11688 11687  0 02:01 pts/1    00:00:00 [zombie-test.pl]
  charlee  11691 11687  0 02:01 pts/1    00:00:00 [zombie-test.pl]
  charlee  11695 11687  0 02:01 pts/1    00:00:00 [zombie-test.pl]
  

  如何避免僵尸进程?当子进程结束时,系统会向父进程发送 SIGCHLD 信号。  父进程只要在处理这个信号时回收子进程占用的资源即可。

利用 waitpid 回收僵尸进程
  一个方法就是在父进程中利用 waitpid 函数。该函数回收指定进程的资源,  并返回已结束进程的进程id。若指定进程不存在,则返回 -1。 我们可以通过调用 waitpid(-1, WNOHANG) 来回收所有子进程。  Perl 提供了全局变量 %SIG,只要设置该变量即可安装信号处理程序。 下面的 waitpid_test1.pl  演示了使用方法。完整的代码可以从本文的附件中下载。
  

use POSIX ":sys_wait_h";  $SIG{CHLD} = \&REAPER;
  sub REAPER {
  my $pid;
  while (($pid = waitpid(-1, WNOHANG)) > 0) {
  # 进行一些处理
  }
  }
  

  执行这个程序并用 ps -ef 查看进程,可以发现僵尸进程不再出现了。
  不过上面这个程序有个问题。Linux的信号是不能排队的,  如果信号到达进程时进程不能接收该信号,这个信号就会丢失。 REAPER 中包含比较耗时的 while 循环,如果在 REAPER 执行过程中  发生 SIGCHLD 信号,这个信号就会丢失。为了避免这种情况, 我们可以尽量减少信号处理的执行时间。参考下面的  waitpid_test2.pl。
  

our $zombies = 0;                 # 记录系统中僵尸进程的数目  $SIG{CHLD} = sub { $zombies++ };  # 信号处理程序中仅仅统计僵尸进程数目
  # 主程序
  while (1) {
  if (fork() == 0) {
  &child;    # 如果当前进程是子进程,则执行 child 函数
  exit;      # 并退出
  } else {
  &REAPER if $zombies;
  sleep 5;   # 如果是父进程,则睡眠 5 秒
  }
  }
  

  实际上,waitpid_test2.pl 并不能及时回收结束的子进程——  由于 REAPER 在主程序中执行,如果子进程结束时主程序尚未执行到 REAPER 一行,  那么系统中可能会出现相当数量的僵尸进程,直到主程序执行 REAPER 为止。 不过一般情况下这种方法已经足够用了。

忽略 SIGCHLD 回收僵尸进程
  另一个比较简单的方法就是直接忽略 SIGCHLD  信号,系统会自动回收结束的子进程。 参见下面的 ignore_sigchld_test.pl。
  

$SIG{CHLD} = 'IGNORE';  # 忽略 SIGCHLD 信号  

  与前面的 waitpid 方法相比,此方法虽然方便,  但缺点就是父进程无法在子进程结束时做些处理。 可根据实际需要选择最合适的方法。
  本文源代码下载 perl-source.zip
  ################################################
  
#!/usr/bin/perl
  
#------------------------------------
  
# ignore_sigchld_test.pl
  
$SIG{CHLD} = 'IGNORE';  # 忽略 SIGCHLD 信号
  

  
sub child {
  
print "I'm child, pid=$$.\n";
  
}
  

  
while (1) {
  
if (fork() == 0) {
  
&child;     # 如果当前进程是子进程,则执行 child 函数
  
exit;       # 并退出
  
} else {
  
sleep 5;    # 如果是父进程,则睡眠 5 秒
  
}
  
}
  
################################################



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-560084-1-1.html 上篇帖子: Perl中的$/ 下篇帖子: perl子进程给父进程传数据
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表