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.plprint "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.plsub child { print "I'm child, pid=$$.\n";}while (1) { if (fork() == 0) { &child; # 如果当前进程是子进程,则执行 child 函数 exit; # 并退出 } else { sleep 5; # 如果是父进程,则睡眠 5 秒 }}
该程序每隔 5 秒创建一个子进程,子进程输出一行文字后退出。 执行该程序片刻之后,从其他终端用 ps -ef 命令可以看到进程状态。 标有 <defunct> 的就是僵尸进程。
charlee 11687 10870 0 02:01 pts/1 00:00:00 /usr/bin/perl perl/zombie-test.plcharlee 11688 11687 0 02:01 pts/1 00:00:00 [zombie-test.pl] <defunct>charlee 11691 11687 0 02:01 pts/1 00:00:00 [zombie-test.pl] <defunct>charlee 11695 11687 0 02:01 pts/1 00:00:00 [zombie-test.pl] <defunct>
如何避免僵尸进程?当子进程结束时,系统会向父进程发送 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 秒
}
}################################################
分享到:
相关推荐
Perl提供了对进程控制的支持,能够方便地创建子进程执行任务。 - **fork函数**:用于创建新的子进程。 ```perl my $pid = fork(); if (defined $pid) { # 父进程 wait(); # 等待子进程结束 } else { # 子进程 ...
在Perl中,处理进程是非常常见的操作,本文将重点介绍Perl中的几个关键函数,包括进程启动、进程终止以及进程控制。 1. 进程启动函数: - `eval`:这个函数允许你执行一段Perl代码字符串。它在正确执行时会清空...
3. **并发编程**:介绍Perl的线程支持,以及如何实现并发任务,如fork、threads模块的使用。 4. **高级正则表达式技巧**:更深入地探讨正则表达式的高级用法,如后向引用、条件匹配和递归模式。 5. **Perl与Web...
6. **并发编程**:Perl支持线程(threads)和进程(fork)实现并发,可以使用`threads`模块创建线程,`fork`函数创建子进程。 7. **国际化和本地化**:Perl的`Text::Wrap`模块用于文本换行,`Getopt::Long`处理...
用fork开展地下工作 发送及接收信号 习题 第十七章高级perl技巧 用eval捕获错误 用grep来筛选列表 用map对列表进行转换 不带引号的哈希键 切片 习题 附录a习题解答 附录b超越小骆驼...
10. 并发编程:Perl支持线程(threads)和fork进程来实现并发执行,也可以使用POE或AnyEvent等事件驱动库来处理异步任务。 学习Perl编程,不仅要掌握上述基础知识,还需要通过实践编写各种类型的程序来提高技能。...
fork 函数是 Perl 中的一个内置函数,用于创建一个新的进程。其调用语法为 procid = fork(),其中 procid 为子进程的进程 ID 号。例如: ```perl $retval = fork(); if ($retval == 0) { # this is the child ...
以上介绍了Perl中一些常用的进程处理函数,包括进程启动、进程终止和进程控制相关的函数。这些函数是Perl程序设计中非常重要的组成部分,尤其是在需要进行多进程编程或者系统管理等场景下。通过合理地使用这些函数,...
8. **过程和并发编程**:Perl支持多种并发模型,如线程和 fork。书中展示了如何在Perl中进行并发编程,以实现多任务并行执行。 9. **系统交互**:Perl可以方便地与操作系统进行交互,执行外部命令,管理进程等。书...
3. 进程控制:可以创建子进程,使用`fork()`, `wait()`, `waitpid()`等函数。 4. 高级特性:如信号处理、套接字编程、进程间通信(IPC)等。 七、正则表达式 Perl中的正则表达式极其强大,可以进行复杂的文本匹配和...
在并发编程上,Perl支持线程和 fork 进程,可以编写多任务并行的程序。 面向对象编程也是Perl的重要特性,书中会介绍如何定义类、创建对象,以及继承、封装和多态等面向对象的基本概念。Perl5引入了MOP(Meta ...
针对从CGI迁移到mod_perl过程中可能遇到的问题提供指导。 - **2.6.1 探索Apache::Registry的秘密** - 了解Apache::Registry如何管理脚本的执行环境。 - **2.6.2 第一个迷团** - 解决从CGI到mod_perl移植时的第一...
Perl 中的进程处理函数主要包括 eval、system、fork、pipe 和 exec 函数。这些函数都是 Perl 语言中非常重要的组件,它们可以帮助开发者处理进程、进程通信、进程同步等问题。 1.eval 函数 eval 函数可以将字符串...
- **创建子进程**:使用 `fork` 函数创建子进程。 - **执行外部命令**:使用 `system` 函数执行外部命令。 #### Perl CGI 编程 - **简介**:CGI(Common Gateway Interface,通用网关接口)是一种标准协议,用于让 ...
9. **并发编程**:Perl可以使用fork、threads模块进行多线程编程,或使用POE等事件驱动框架。 10. **脚本自动化**:结合Perl的系统管理工具,如系统监控、日志分析、定时任务等。 通过研究这些源码,读者不仅可以...
Perl5支持多进程和多线程编程,可以利用fork创建子进程,通过threads模块创建和管理线程。这在处理并发任务和高负载场景时非常有用。 十、脚本调试和测试 Perl5的Debug模块和Test::More模块分别用于程序调试和编写...
书中将详述这些操作,以及如何利用重定向和管道进行进程间通信。 6. **正则表达式**:Perl的正则表达式功能强大,广泛用于文本匹配和替换。书中会深入讲解正则表达式语法,包括特殊字符、修饰符、预定义模式等。 7...
11. **并发编程**:介绍Perl中的线程、进程和异步编程,包括如何利用fork和threads模块进行并发处理。 12. **配置文件处理**:教授如何读取和写入配置文件,如使用Config::Simple或 AppConfig 模块。 以上内容仅是...
- 系统调用函数:system(执行外部命令)、exec(替换当前进程)、fork(创建子进程)。 ### 正则表达式 Perl的正则表达式功能强大,支持各种模式匹配和文本替换操作。正则表达式的语法包括: - 字符类:[a-z]...