Shell 实现多任务并发

原创 2016年05月17日 09:02:00

实现思路

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

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 "\n" 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\n");
}

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

相关文章推荐

[Linux]Linux Shell多进程并发以及并发数控制

Unix是一个多任务系统,允许多用户同时运行多个程序。shell的元字符&提供了在后台运行不需要键盘输入的程序的方法。输入命令后,其后紧跟&字符,该命令就会被送往到linux后台执行,而终端又可以继续...

shell并发

最基本的并发执行,放在后台并发执行a、b,c、dshell A& shell B& wait shell C& shell D&#!/bin/bash #创建管道文件,文件操作符绑定,删除管道文件 m...

Shell脚本中的并发(1)

主要记录一下Shell脚本中的命令的并发和串行执行。 默认的情况下,Shell脚本中的命令是串行执行的,必须等到前一条命令执行完后才执行接下来的命令,但是如果我有一大批的的命令需要执行,而且互相又没有...

shell脚本并发执行

#!/bin/bash for(( i = 0; i < ${count}; i++ )) do { commands1 ...

bash shell实现并发多进程操作

前言 目前我掌握的基本语言,php(最为熟悉,项目里代码都是用其实现),bash shell(运维利器),c(acm专用),这里面能实现多线程的貌似只有c,但是我c只是用来学习和实现算法和数据结构,...

shell中的多进程【并发】

根据我个人的理解, 所谓的多进程 只不过是将多个任务放到后台执行而已,很多人都用到过,所以现在讲的主要是控制,而不是实现。 先看一个小shell:   看执行结果:   很明显是8s...

shell队列实现线程并发控制

需求:并发检测1000台web服务器状态(或者并发为1000台web服务器分发文件等)如何用shell实现? 方案一:(这应该是大多数人都第一时间想到的方法吧) 思路:一个for循环1000次...
  • zk1113
  • zk1113
  • 2016年03月30日 13:38
  • 919

使用shell并行执行多个脚本

有没有一种比较通用的并行执行多个SQL脚本的方法呢?每种数据库都提供命令行接口执行SQL语句,因此最容易想到的就是通过初始化多个并发的会话并行执行,每个会话运行一个单独的查询,用来抽取不同的数据部分。...
  • wzy0623
  • wzy0623
  • 2016年12月28日 11:31
  • 3024

shell 同时运行脚本里多个互不干扰的指令

今天在搞一个需求,需要在shell里同时运行两个互不干扰的指令, 主要是在脚本里要让一个apk同时在不同的设备上运行,但是如果在脚本里将命令一行一行堆砌起来的话,这样的代码是串行的,我的mentor...

linux shell 多个命令一起执行的几种方法

在命令行可以一次执行多个命令
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Shell 实现多任务并发
举报原因:
原因补充:

(最多只允许输入30个字)