Linux(编程-进程基础):10:---进程的竞争条件、轮询

一、概念

  • 当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程运行的顺序时,则我们认为这发生了竞态条件(race conditions)
  • 如果在fork之后的某种逻 辑显式或隐式地依赖于在fork之后是父进程先运行还是子进程先运行,那么fork函数就会是竞态条件活跃的滋生地
  • 通常,我们不能预料哪一个进程先运行。即使知道哪一个进程先运行, 那么在该进程开始运行后,所发生的事情也依赖于系统负载以及内核的调度算法

二、案例

https://blog.csdn.net/qq_41453285/article/details/88992605文章的waitpid()函数的案例中:

  • 当第二个子进程打印其父进程ID时,我们看到了一个潜在的竞态条件。如 果第二个子进程在第一个子进程之前运行,则其父进程将会是第一个子进程。但是,如果第一 个子进程先运行,并有足够的时间到达并执行exit,则第二个子进程的父进程就是init
  • 即使在 程序中调用sleep,也不能保证什么。如果系统负担很重,那么在第二个子进程从sleep返回时, 可能第一个子进程还没有得到机会运行
  • 这种形式的问题很难调试,因为在大部分时间,这种问题并不出现

三、轮询

  • 如果一个进程希望等待一个子进程终止,则它必须调用wait函数。如果一个进程要等待其父进程终止,则可使用下列形式的循环
  • 这种形式的循环成为轮询(polling),它的问题是它浪费了CPU时间,因为调用者每隔1秒都被唤醒,然后进行条件测试
while (getppid() != 1)
    sleep(1);

四、信号

  • 为了避免竞争态条件和轮询,在多个进程之间需要有某种形式的信号发送和接收的方法。在UNIX中可以使用信号机制,在后面将说明它的一种用法。各种形式的进程间通信 ( IPC )也可使用, 在后面会介绍

五、案例一

  • 在父、子进程的关系中,常常出现下述情况。在fork之后,父、子进程都有一些事情要做。  例如,父进程可能以子进程I D更新日志文件中的一个记录,而子进程则可能要为父进程创建一 个文件。在本例中,要求每个进程在执行完它的一套初始化操作后要通知对方,并且在继续运 行之前,要等待另一方完成其初始化操作。这种情况可以描述如下
TELL_WAIT(); /* set things up for TELL_xxx & WAIT_xxx */

if ((pid = fork()) < 0) 
{
    perror("fork error");
} 
else if (pid == 0) /* child */
{ 
/* child does whatever is necessary ... */
TELL_PARENT(getppid()); /* tell parent we’re done */
WAIT_PARENT(); /* and wait for parent */
/* and the child continues on its way ... */

exit(0);
}

/* parent does whatever is necessary ... */
TELL_CHILD(pid); /* tell child we’re done */
WAIT_CHILD(); /* and wait for child */

/* and the parent continues on its way ... */
exit(0);
  • 假定在头文件中定义了各个需要使用的变量。五个例程TELL_WAIT、TELL_PARENT、TELL_CHILD、WAIT_PARENT以及WAIT_CHILD可以是宏,也可以是函数

在后面的一些章中会说明实现这些TELL和TELL例程的不同方法:后面会说明用信号的一种实现。下面先看一个使用这五个例程的实例:

  • 下列程序输出两个字符串:一个由子进程输出,一个由父进程输出。因为输出依赖于内核 使进程运行的顺序及每个进程运行的时间长度,所以该程序包含了一个竞态条件
static void charatatime(char *);
int main(void)
{
    pid_t pid;
    if ((pid = fork()) < 0) {
        perror("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);
}

运行结果

  • 在程序中将标准输出设置为不带缓存的,于是每个字符输出都需调用一次write。本例的 目的是使内核能尽可能多次地在两个进程之间进行切换,以例示竞态条件。 (如果不这样做, 可能也就决不会见到下面所示的输出。没有看到具有错误的输出并不意味着竞态条件不存在, 这只是意味着在此特定的系统上未能见到它。 )下面的实际输出说明该程序的运行结果是会改 变的。

  • 修修改上面程序,使其使用TELL和WAIT函数,于是形成了下面的程序
static void charatatime(char *);
int main(void)
{
    pid_t pid;
    TELL_WAIT();

    if ((pid = fork()) < 0) {
        perror("fork error");
    } else if (pid == 0) {
        WAIT_PARENT(); /* parent goes first */
        charatatime("output from child\n");
    } else {
        charatatime("output from parent\n");
        TELL_CHILD(pid);
    }
    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之后的行改变成下面形式:则子进程先运行

else if (pid == 0) {
    charatatime("output from child\n");
    TELL_PARENT(getppid());
} else {
    WAIT_CHILD(); /* child goes first */
    charatatime("output from parent\n");
}

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/89005626