zoukankan      html  css  js  c++  java
  • Shell 实现多任务并发

    实现思路

    实现一个shell进程库,通过类似于init,run,wait几个简单的命令,就可以迅速实现多进程并发,伪码如下:

    process_init # 创建进程
    for city in ${cities[*]}
    do
    cmd="handler $city"
    process_run $cmd 
    done
    process_wait # 等待进程

    原理解析

    在实现C++线程库的时候,通常会有一个任务队列,线程从队列中取任务并运行。在实现shell进程库的时候,采用了类似原理,通过一个有名管道充当任务队列。严格来说,并不是一个任务队列,而是一个令牌桶。进程从桶中取得令牌后才可以运行,运行结束后将令牌放回桶中。没有取得令牌的进程不能运行。令牌的数目即允许并发的最大进程数。

     

    管道

    主要思路:通过mkfifo创建一个有名管道,将管道与一个文件描述符绑定,通过往管道中写数据的方式,控制进程数量。

    function _create_pipe()
    {
    _PROCESS_PIPE_NAME=$(_get_uid)
    
    mkfifo ${_PROCESS_PIPE_NAME}
    eval exec "${_PROCESS_PIPE_ID}""<>${_PROCESS_PIPE_NAME}"
    
    for ((i=0; i < $_PROCESS_NUM; i++))
    do
    echo -ne "
    " 1>&${_PROCESS_PIPE_ID}
    done
    }

    exec

    exec fd < file #以读方式打开文件,并关联文件描述符fd
    exec fd > file #以写方式打开文件,并关联文件描述符fd
    exec fd <> file #以读写方式打开文件,并关联文件描述符
    # 测试
    exec 8>file
    echo "hello word!" 1>&8

    eval
    为了让程序有一定的扩展性,不想写死fd,因而引入了变量。

    file_fd=8
    exec ${file_fd}>file
    # 测试
    bash test.sh
    test.sh: line 7: exec: 8: not found


    原因:shell在重定向操作符(<、>)左边不进行变量展开。因而引入eval命令,强制shell进行变量展开。 
    eval exec "${fd}>file"简单的说,eval将右边参数整体作为一个命令,进行变量的替换,然后将替换后的输出结果给shell去执行。

    进程关系

    命令执行

    function process_run()
    {
    cmd=$1
    if [ -z "$cmd" ]; then
    echo "please input command to run"
    _delete_pipe 
    exit 1
    fi
    
    _process_get
    {
    $cmd
    _process_post
    }&
    }

    _process_get从管道中取得一个令牌,创建一个进程执行任务,任务执行完毕后,通过_process_post将令牌放回管道。

    进程创建

    chengsun@153_92:~/test> bash process_util.sh
    chengsun@153_92:~/test> pstree -a
    
    |`-sshd
    |    |-bash
    |    |   `-bash process_util.sh         #爷爷
    |    |       |-bash process_util.sh     #爸爸
    |    |       |   `-a.out                #儿子
    |    |       |-bash process_util.sh        
    |    |       |   `-a.out                   
    |    |       `-bash process_util.sh
    |    |           `-a.out

    脚本运行后,通过pstree命令,得到如上父子进程关系。稍微做一下解释:当运行脚本bash process_util.sh的时候,创建一个shell进程,相当于爷爷被创建起来,而遇到{ command1; command2 } &时,shell 又创建一个子shell进程(爸爸进程)处理命令序列;而对于每一个外部命令,shell都会创建一个子进程运行该命令,即儿子进程被创建。

    困惑:为什么处理{ command1; command2; } &需要单独创建子进程? 
    按照bash manual说法,{ list }并不会创建一个新的shell来运行命令序列。但由于加入&,代表将命令族放入后台执行,就必须新开subshell,否则shell会阻塞。

    进程组

    chengsun@153_92:~/test> ps -f -e -o pid,ppid,pgid,comm
    
     PID  PPID  PGID  COMMAND
    24904 21976 24904 bash
    19885 24904 19885  \_ bash            # 爷爷
    19893 19885 19885      \_ bash        # 爸爸
    19894 19893 19885      |   \_ a.out   # 儿子
    19895 19885 19885      \_ bash
    19896 19895 19885      |   \_ a.out
    19897 19885 19885      \_ bash
    19898 19897 19885          \_ a.out

    Shell 将运行process_util的一堆进程置于一个进程组中。其中爷爷进程作为该进程组的组长,pid == pgid。

    wait
    wait pid:阻塞等待某个进程结束; 如果没有指定参数,wait会等待所有子进程结束。

    清理函数

    允许任务通过CTRL+C方式提前结束,因而需要清理函数

    信号

    trap 
    类似C语言signal函数,为shell脚本注册信号处理函数。trap arg signals,其中signals为注册的信号列表,arg为收到信号后执行某个命令。

    function Print
    {
    echo "Hello World!"
    }
    
    trap Print SIGKILL

    kill 
    kill 命令给进程或进程组发送信号;kill pid 给进程发送默认信号SIGTERM, 通知程序终止执行。

    void sig_handler(int signo)
    {
    printf("sigterm signal
    ");
    }
    
    int main()
    {
        signal(SIGTERM, sig_handler);
        sleep(100);
    
    return 0;
    }
    chengsun@153_92:~/test> ./a.out &
    [1] 19254
    chengsun@153_92:~/test> kill 19254
    sigterm signal

    kill 0:给当前进程组发送默认信号SIGTERM

    chengsun@153_92:~/test> man kill
    0  All processes in the current process group are signaled.

    清理

    function _clean_up
    {
    # 清理管道文件
        _delete_pipe
    
    kill 0
    kill -9 $$
    }
    
    trap _clean_up SIGINT SIGHUP SIGTERM SIGKILL

    kill -9 $$ 非常重要

     

    实际上,最上层是爷爷进程,当发送Ctrl + C命令的时候,爷爷进程捕获SIGINT信号,调用_clean_up函数。爷爷进程对整个进程组发送SIGTERM信号,并调用kill -9结束自己。爸爸进程接收SIGTERM信号,同时也发送SIGTERN给整个进程组,如果没有kill -9,爸爸进程始终无法结束,进入无限递归环节。儿子为CPP二进制程序,内部没有捕获SIGTERM,该信号默认动作是结束进程。

    使用范例

    # file: run.sh
    #!/bin/sh
    
    #load process library
    source ./process_util.sh
    
    function handler()
    {
        city=$1
        ./main ${city}
    }
    
    process_init 23
    for city in $cities
    do
        cmd = "handler $city"
        process_run "$cmd"
    done
    process_wait

    ————————————————
    版权声明:本文为CSDN博主「spch2008」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/spch2008/article/details/51433353

  • 相关阅读:
    进程及进程调度
    加强版水王:找出出现次数刚好是一半的数字
    寻找最小的k个数(四种方法)
    Count Primes
    深入理解计算机系统结构——并发编程
    深入理解计算机系统——系统级I/O
    深入理解计算机系统结构——虚拟存储器
    老生常谈:关于undo表空间的使用率
    OSW 快速安装部署
    Oracle参数设置之set与reset的实际案例
  • 原文地址:https://www.cnblogs.com/cangqinglang/p/12109174.html
Copyright © 2011-2022 走看看