从一段代码看fork()函数及其引发的竞争

首先来看一段从《UNIX环境高级编程》中摘录的一段非常有意思的代码。借此我们再来谈谈fork()函数的一些问题。

#include "apue.h"

static void charatatime(char*);

int
main(void)
{
  pid_t	pid;

  if((pid=fork())<0){
    err_sys("fork error");
  }else if(pid==0){
    charatatime("output from child\n");
  }else{
    charatatime("output from parent\n");
  }
  exit(0);
}

static void
charatatime(char *str)
{
  char	*ptr;
  int	c;

  setbuf(stdout,NULL);                              /*set unbuffered*/
  for(ptr=str;(c=*ptr++)!=0;)
    putc(c,stdout);
}

这段代码到底干了些啥呢?其实非常简单,首先用fork()函数生成了一个子进程。其实子进程可以看成是父进程的一个复制。那么在如上的一段代码中,怎么判断是子进程还是父进程在执行呢?这时候我们就要来看看fork()函数的返回值了。

fork()的返回值:

当fork被调用之后,父子进程都从fork()之后开始执行。当然,父子进程要干的事情是不一样的,但是前面说了,子进程就是父进程的一个复制,它们其实是共享一个代码段的。这个时候,我们就要依靠fork()的返回值来判断当前运行的程序是父进程还是子进程了。fork()函数是个非常有意思的家伙。它只被调用了一次,但是却有两个返回值,分别返回到父子进程中。

在父进程中,fork()返回的是子进程的进程ID。值得注意的是,只有在fork()函数的时候,父进程才能得到子进程的ID,否则的话就没有机会了。因为一个父进程可以有多个子进程,想要通过一个函数,得到某个确切的子进程的进程ID显然是比较困难的。与父进程不同的是,子进程通常只有一个父进程,因此可以通过一个叫getppid()的函数,找到自己父进程的ID。

而fork()在子进程中的返回值是0.这又是为什么呢?因为0通常是系统保留的进程号,因此不可能出现子进程的进程号为0的情况。正如上面的代码显示的那样,当pid==0的时候传递给子函数的字符数是“output from child”,否则那就是在父进程中,传递的字符串自然也成了“output from parent”。

接下来还有一个问题,那就是,当fork()之后,父子进程其实可以看成是两个独立的进程了。那到底是先执行父进程呢?还是先执行子进程呢?因此我们接着来谈谈进程间的竞争问题。

进程间的竞争(race condition):

那父子进程到底是谁先运行呢?其实一般来说,这是无法预测的,这要看内核的调度算法等一系列其他的因素。我们可以会过来看看上面的代码。在父子进程共同调用的charatatime函数中,我们首先用setbuf取消了标准I/O的缓冲,这样在下面的for循环中,只要putc一次,就会有相应的字符显示在shell上。根据上面的分析,我们可以预测的是,两个字符串可能并不会按照先后顺序完整地输出。因为进程间很可能进行切换,一个字符串可能还没输完,内核就转而执行另一个字符串的输出了。因此显示的shell中显示的结果很可能是交叉输出的字符串。下面的图就为我们展示了结果:

很显然,输出的结果是不可预测的,有时候是比较规则的输出,但有些时候就凌乱了。而这,就是进程间的竞争(race condition)。当然,解决竞争的方法有很多,我们可以通过信号(signal)以及进程间通信(IPC)等待方式,来解决竞争的问题。这些就放到以后再说啦!

参考文献:《Advanced Programming in the UNIX Environment》

时间: 2024-12-25 15:21:11

从一段代码看fork()函数及其引发的竞争的相关文章

用一段代码去了解函数的结构

1,关于构造函数:当我们要声明一个函数的时候,函数本身就是一个对象,在这个对象上可以定义自身属性及方法,这些属性属于静态方法,属性:此时的函数对象是由Function构造函数构建的,Function可定义原型,Function.prototype,在此原型对象定义的方法,属性,在function声明称的函数可访问:function fn()声明的函数对象,自带原型对象,可在fn.prototype定义方法,属性 2,实例化的构造函数: 通过new fn()实例化的函数类型对象,自身拥有执行上下文

从一段代码看java对象初始化中属性的初始化

在java代码中,当new一个对象的时候,先是创建了一个属性为零值的对象,然后进行初始化: ```javapublic class Test { private Date date = new Date(); public static void main(String[] args) throws InterruptedException { Test t = new Test(); for (int i = 0; i < 5; i++) { Thread.sleep(1000); Syste

Linux多任务编程之二:fork()函数及其基础实验(转)

来源:CSDN  作者:王文松 转自Linux公社 fork()函数 在 Linux 中创建一个新进程的唯一方法是使用fork()函数.fork()函数是 Linux 系统中一个非常重要的函数,和咱们以前遇到过的函数由一些区别,因为它看起来执行一次却返回两个值,这又作何解释?不着急,慢慢看. 函数说明 fork()函数用于从已存在的一个进程中创建一个新的进程,新进程称为子进程,而原进程称为父进程.使用fork()函数得到的子进程是父进程的 一个复制品,它从父进程处继承了整个进程的地址空间,包括进

从一段代码谈GetPrivateProfileString的深坑

**总结:GetPrivateProfileString注意文件是否存在, 文件不存在或不具有r读权限会在运行时报内存错误.** 失败源代码如下: string programDir = GetOcxPath(file);//得到ini配置文件路径 ret = GetPrivateProfileString(app,key,"",returnString,MAX_PATH,"c:\\base64bmp_config.ini"); 这段代码看起来没任何问题,在MFC

从linux0.11中起动部分代码看汇编调用c语言函数

上一篇分析了c语言的函数调用栈情况,知道了c语言的函数调用机制后,我们来看一下,linux0.11中起动部分的代码是如何从汇编跳入c语言函数的.在LINUX 0.11中的head.s文件中会看到如下一段代码(linux0.11的启动分析部分会在另一部分中再分析,由于此文仅涉及c与汇编代码的问题,). after_page_tables: pushl $0 # These are the parameters to main :-) pushl $0 pushl $0 pushl $L6 # re

都怪当初看了这6段代码,造成了如今的深度学习!

回忆当初我们看到的这6段代码,造就了我们如今的深度学习,这6段代码的创作者及其取得 的这些辉煌成就的那些故事.这些故事都对应的有简单的代码示例,小伙伴们在FloydHub 和 GitHub 上找到相关代码. 提示一下:要运行 FloydHub 上的代码,需要确保你的电脑上已经安装了Floyd命令工具,并且复制代码保存到本地计算机.在本地计算机示例项目中初始化命令行界面之后,您就可以运行以下命令在 FloydHub 上启动项目: 最小二乘法 所有的深度学习算法都始于下面这个数学公式(我已将其转成

c#转Java ,如何折叠一段代码使整个代码看起来简洁

Java netBeans/Eclips 如何折叠一段代码使整个代码看起来简洁 最近刚用Java,以前写C#的时候,通过region操作可以使一段代码折叠起来,使整段程序缩成一行,看起来清爽了许多,现在用netBeans, 发现自带的代码折叠功能只能折叠一个整个的method,不能选择一段进行折叠,请问能实现类似于C#的功能吗?如 #region 很长的代码 #endregion NetBeans内Editor设置了类似的功能. 只需要在模块开始注释以//<editor-fold>开始, 在模

js中闭包来实现bind函数的一段代码的分析

今天研究了一下bind函数,发现apply和call还可以有这样的妙用,顺便巩固复习了闭包. 1 var first_object = { 2 num: 42 3 }; 4 var second_object = { 5 num: 24 6 }; 7 function multiply(mult) { 8 return this.num * mult; 9 } 10 Function.prototype.bind = function(obj) { 11 var method = this, 1

linux中fork()函数详解[zz]

转载自:http://www.cnblogs.com/york-hust/archive/2012/11/23/2784534.html 一.fork入门知识 一个进程,包括代码.数据和分配给进程的资源.fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事. 一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间.然后把原来的进程的所有值都复制到新的新进程中,只有