Shell 實現(xiàn)多任務(wù)并發(fā)的示例代碼
實現(xiàn)思路
實現(xiàn)一個shell進(jìn)程庫,通過類似于init
,run
,wait
幾個簡單的命令,就可以迅速實現(xiàn)多進(jìn)程并發(fā),偽碼如下:
process_init # 創(chuàng)建進(jìn)程 for city in ${cities[*]} do cmd="handler $city" process_run $cmd done process_wait # 等待進(jìn)程
原理解析
在實現(xiàn)C++線程庫的時候,通常會有一個任務(wù)隊列,線程從隊列中取任務(wù)并運(yùn)行。在實現(xiàn)shell進(jìn)程庫的時候,采用了類似原理,通過一個有名管道
充當(dāng)任務(wù)隊列。嚴(yán)格來說,并不是一個任務(wù)隊列,而是一個令牌桶。進(jìn)程從桶中取得令牌后才可以運(yùn)行,運(yùn)行結(jié)束后將令牌放回桶中。沒有取得令牌的進(jìn)程不能運(yùn)行。令牌的數(shù)目即允許并發(fā)的最大進(jìn)程數(shù)。
管道
主要思路:通過mkfifo
創(chuàng)建一個有名管道,將管道與一個文件描述符綁定,通過往管道中寫數(shù)據(jù)的方式,控制進(jìn)程數(shù)量。
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 #以讀方式打開文件,并關(guān)聯(lián)文件描述符fd exec fd > file #以寫方式打開文件,并關(guān)聯(lián)文件描述符fd exec fd <> file #以讀寫方式打開文件,并關(guān)聯(lián)文件描述符
# 測試 exec 8>file echo "hello word!" 1>&8
eval
為了讓程序有一定的擴(kuò)展性,不想寫死fd,因而引入了變量。
file_fd=8 exec ${file_fd}>file
# 測試 bash test.sh test.sh: line 7: exec: 8: not found
原因:shell在重定向操作符(<、>)左邊不進(jìn)行變量展開。因而引入eval命令,強(qiáng)制shell進(jìn)行變量展開。 eval exec "${fd}>file"
簡單的說,eval將右邊參數(shù)整體作為一個命令,進(jìn)行變量的替換,然后將替換后的輸出結(jié)果給shell去執(zhí)行。
進(jìn)程關(guān)系
命令執(zhí)行
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
從管道中取得一個令牌,創(chuàng)建一個進(jìn)程執(zhí)行任務(wù),任務(wù)執(zhí)行完畢后,通過_process_post
將令牌放回管道。
進(jìn)程創(chuàng)建
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
腳本運(yùn)行后,通過pstree
命令,得到如上父子進(jìn)程關(guān)系。稍微做一下解釋:當(dāng)運(yùn)行腳本bash process_util.sh
的時候,創(chuàng)建一個shell進(jìn)程,相當(dāng)于爺爺被創(chuàng)建起來,而遇到{ command1; command2 } &
時,shell 又創(chuàng)建一個子shell進(jìn)程(爸爸進(jìn)程)處理命令序列;而對于每一個外部命令,shell都會創(chuàng)建一個子進(jìn)程運(yùn)行該命令,即兒子進(jìn)程被創(chuàng)建。
困惑:為什么處理{ command1; command2; } &
需要單獨(dú)創(chuàng)建子進(jìn)程?
按照bash manual說法,{ list }
并不會創(chuàng)建一個新的shell來運(yùn)行命令序列。但由于加入&
,代表將命令族放入后臺執(zhí)行,就必須新開subshell,否則shell會阻塞。
進(jìn)程組
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 將運(yùn)行process_util的一堆進(jìn)程置于一個進(jìn)程組中。其中爺爺進(jìn)程作為該進(jìn)程組的組長,pid == pgid。
wait
wait pid:
阻塞等待某個進(jìn)程結(jié)束; 如果沒有指定參數(shù),wait
會等待所有子進(jìn)程結(jié)束。
清理函數(shù)
允許任務(wù)通過
CTRL+C
方式提前結(jié)束,因而需要清理函數(shù)
信號
trap
類似C語言signal函數(shù),為shell腳本注冊信號處理函數(shù)。trap arg signals
,其中signals為注冊的信號列表,arg為收到信號后執(zhí)行某個命令。
function Print { echo "Hello World!" } trap Print SIGKILL
kill kill
命令給進(jìn)程或進(jìn)程組發(fā)送信號;kill pid 給進(jìn)程發(fā)送默認(rèn)信號SIGTERM
, 通知程序終止執(zhí)行。
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
:給當(dāng)前進(jìn)程組發(fā)送默認(rèn)信號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 $$
非常重要
實際上,最上層是爺爺進(jìn)程,當(dāng)發(fā)送Ctrl + C
命令的時候,爺爺進(jìn)程捕獲SIGINT
信號,調(diào)用_clean_up
函數(shù)。爺爺進(jìn)程對整個進(jìn)程組發(fā)送SIGTERM
信號,并調(diào)用kill -9
結(jié)束自己。爸爸進(jìn)程接收SIGTERM
信號,同時也發(fā)送SIGTERN
給整個進(jìn)程組,如果沒有kill -9
,爸爸進(jìn)程始終無法結(jié)束,進(jìn)入無限遞歸環(huán)節(jié)。兒子為CPP二進(jìn)制程序,內(nèi)部沒有捕獲SIGTERM
,該信號默認(rèn)動作是結(jié)束進(jìn)程。
使用范例
# 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
到此這篇關(guān)于Shell 實現(xiàn)多任務(wù)并發(fā)的示例代碼的文章就介紹到這了,更多相關(guān)Shell 多任務(wù)并發(fā)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Linux Shell 數(shù)組的創(chuàng)建及使用技巧
這篇文章主要介紹了Linux Shell 數(shù)組的創(chuàng)建及使用技巧,本文講解了數(shù)組定義、數(shù)組讀取與賦值以及特殊使用,需要的朋友可以參考下2015-07-07Shell命令中的特殊替換、模式匹配替換、字符串提取和替換的實現(xiàn)
本文主要介紹了Shell命令中的特殊替換、模式匹配替換、字符串提取和替換的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03