zoukankan      html  css  js  c++  java
  • 进程间通信之协同进程

    UNIX系统过滤程序从标准输入读取数据,对其进行适当处理后写到标准输出。几个过滤程序通常在shell管道命令行中线性地连接。当一个程序产生某个过滤程序的输入,同时又读取该过滤程序的输出时,则过滤程序就成为协同进程(coprocess)。

    Korn shell提供了协同进程。Bourne shell、Bourne-again shell和C shell并没有提供按协同进程方式将进程连接起来的方法。协同进程通常在shell的后台运行,其标准输入和标准输出通过管道连接到另一个程序。

    popen只提供连接到另一个进程的标准输入或标准输出的一个单向管道,而对于协同进程,则它有连接到另一个进程的两个单向管道——一个接到其标准输入,另一个则来自其标准输出。我们先要将数据写到其标准输入,经其处理后,再从其标准输出读取数据。

    实例

    进程线创建两个管道:一个是协同进程的标准输入,另一个是协同进程的标准输出。图15-8显示了这种安排。

    未命名

                     图15-8 写协同进程的标准输入,读它的标准输出

    程序清单15-8程序是一个简单的协同进程,它从其标准输入读两个数,计算它们的和,然后将结果写至标准输出。

    程序清单15-8 对两个数求和的简单过滤程序

    #include "apue.h"
    
    int
    main(void)
    {
        int     n, int1, int2;
        char    line[MAXLINE];
    
        while((n = read(STDIN_FILENO, line, MAXLINE)) > 0)
        {
            line[n] = 0;    /* null terminate */
            if(sscanf(line, "%d%d", &int1, &int2) == 2)
            {
                sprintf(line, "%d
    ", int1 + int2);
                n = strlen(line);
                if(write(STDOUT_FILENO, line, n) != n)
                    err_sys("write error");
            }
            else
            {
                if(write(STDOUT_FILENO, "invalid args
    ", 13) != 13)
                    err_sys("write error");
            }
        }
        exit(0);
    }

    对此程序进行编译,将其可执行目标代码存入名为add2的文件。

    程序清单15-9从其标准输入读入两个数之后调用add2协同进程,并将协同进程送来的值写到其标准输出。

    程序清单15-9 驱动add2过滤程序的程序

    #include "apue.h"
    
    static void sig_pipe(int);    /* our signal handler */
    
    int
    main(void)
    {
        int      n, fd1[2], fd2[2];
        pid_t    pid;
        char     line[MAXLINE];
    
        if(signal(SIGPIPE, sig_pipe) == SIG_ERR)
            err_sys("signal error");
    
        if(pipe(fd1) < 0  || pipe(fd2) < 0)
            err_sys("pipe error");
    
        if((pid = fork()) < 0)
        {
            err_sys("fork error");
        }
        else if(pid > 0)    /* parent */
        {
            close(fd1[0]);
            close(fd2[1]);
            while(fgets(line, MAXLINE, stdin) != NULL)
            {
                n = strlen(line);
                if((m = write(fd1[1], line, n)) != n);   /* 无论这里的if条件是真是假都执行err_sys(“write error to pipe”);不知为何 */
                {
                    err_sys("write error to pipe");
                }
                if((n = read(fd2[0], line, MAXLINE)) < 0)
                    err_sys("read error from pipe");
                if(n == 0)
                {
                    err_msg("child closed pipe");
                    break;
                }
                line[n] = 0;    /* null terminate */
                if(fputs(line, stdout) == EOF)
                    err_sys("fputs error");
            }
            
            if(ferror(stdin))
                err_sys("fgets error on stdin");
            exit(0);
        }
        else    /* child */
        {
            close(fd1[1]);
            close(fd2[0]);
            if(fd1[0] != STDIN_FILENO)
            {
                if(dup2(fd1[0], STDIN_FILENO) != STDIN_FILENO)
                    err_sys("dup2 error to stdin");
                close(fd1[0]);
            }
    
            if(fd2[1] != STDOUT_FILENO)
            {
                if(dup2(fd2[1], STDOUT_FILENO) != STDOUT_FILENO)
                    err_sys("dup2 error to stdout");
                close(fd2[1]);
            }
            if(execl("./add2", "add2", (char *)0) < 0)
                err_sys("execl error");
        }
        exit(0);
    }
    
    static void
    sig_pipe(int signo)
    {
        printf("SIGPIPE caught
    ");
        exit(1);
    }

    在程序中创建了两个管道,父、子进程各自关闭它们不需要使用的端口。两个管道一个用作协同进程的标准输入,另一个则用作它的标准输出。子进程调用dup2使管道描述符移至其标准输入和标准输出,然后调用execl。

    实例

    在协同进程add2中,有意地使用了read和writeI/O(UNIX系统调用)。如果使用标准I/O改写该协同进程,其后果是什么呢?程序清单15-10为改写后的版本。

    程序清单15-10 对两个数求和的过滤程序,使用标准I/O

    #include "apue.h"
    
    int
    main(void)
    {
        int      int1, int2;
        char     line[MAXLINE];
    
        while(fgets(line, MAXLINE, stdin) != NULL)
        {    
            if(sscanf(line, "%d%d", &int1, &int2) == 2)
            {
                if(printf("%d
    ", int1 + int2) == EOF)
                    err_sys("printf error");
            }
            else
            {
                if(printf("invalid args
    ") == EOF)
                    err_sys("printf error");
            }
        }
        exit(0);
    }

    若程序清单15-9调用此新的协同进程,则它不再工作。问题出在系统默认的标准I/O缓冲机制上(见http://www.cnblogs.com/nufangrensheng/p/3505307.html)。当调用程序清单15-10所示程序时,对标准输入的第一个fgets引起标准I/O库分配一个缓冲区,并选择缓冲区的类型。因为标准输入是个管道,所以标准I/O库由系统默认是全缓冲的。对标准输出也做同样的处理。当add2从其标准输入读取而发生阻塞时,程序清单15-9程序从管道读时也发生阻塞,于是产生了死锁。

    为此,更改将要运行的协同进程的缓冲类型,在程序清单15-10中的while循环之前加上下面4行:

    if(setvbuf(stdin, NULL, _IOLBF, 0) != 0)
        err_sys("setvbuf error");
    if(setvbuf(stdout, NULL, _IOLBF, 0) != 0)
        err_sys("setvbuf error");

    这些代码行使得当有一行可用时,fgets就返回,并使得当输出一换行符时,printf立即执行fflush操作。

    本篇博文内容摘自《UNIX环境高级编程》(第二版),仅作个人学习记录所用。关于本书可参考:http://www.apuebook.com/

  • 相关阅读:
    C#基础知识之静态和非静态
    C#基础知识之类和结构
    jQuery选择器
    ajax和json的优缺点
    说几条JavaScript的基本规范
    vue中异步请求渲染问题(swiper不轮播)(在开发过程中遇到过什么问题、踩过的坑)
    vue响应数据的原理
    面向对象的几种方式(创建对象的几种方式)
    ES6新特性
    HTML和XHTML的区别
  • 原文地址:https://www.cnblogs.com/nufangrensheng/p/3561379.html
Copyright © 2011-2022 走看看