问题描述
在服务器上提交任务时,需要限制运行的核的数目。程序本身是单线程的,但是不同的输入参数需要跑很多组,粗暴的方法是开多个终端,不断地去提交任务。但这比较麻烦,可以用 shell 实现。
基础
首先看第一种 shell 写法
1 #! /bin/bash 2 3 start=`date +%s` 4 5 for i in {0..3} 6 do 7 echo sucess 8 sleep 2 9 done 10 11 end=`date +%s` 12 echo "time: `expr $end - $start`"
- 如何提取当前时间,date +%s,返回值是从 1970 年至今的秒数。
- for 循环的写法。
- shell 中数学运算,注意 start 与 end 之间需要空格。
该程序的运行结果是 8 s,如果放在后台运行,则只需要 2 s,代码如下
1 #! /bin/bash 2 3 start=`date +%s` 4 5 for i in {0..3} 6 do 7 { 8 echo sucess 9 sleep 2 10 } & 11 done 12 wait 13 14 end=`date +%s` 15 echo "time: `expr $end - $start`"
- 需要使用 {} 将主执行程序包括,用 & 将其放入后台。
- 需要 wait 命令等待所有命令执行完成,不然系统会直接继续往下走。
这样做实现了多线程并发,但是问题是不能控制使用的进程数。因此引入了管道和文件操作符。
管道
- 比如 hexdump Run0035.bin | less 中的 | 就是管道,用于连接两个命令间的数据。
- 管道的特点是,如果管道中没有数据,那么取管道数据的操作就会停滞,直到管道中有数据;同样写管道的操作也是如此,如果没有读取操作,那么写操作也会停滞。意思就是管道需要既有读操作,也有写操作。
- 使用 mkfifo 命令可以创建有名管道
文件操作符
- 通过文件操作符,可以控制 linux 系统中 /proc 文件夹下的内容,从而控制 linux 中进程的运行。
代码
1 #!/usr/bin/env bash 2 3 #设置变量 4 beta=0.25 5 gammaList=(22.0 24.0 26.0 28.0 30.0 32.0 34.0 36.0 38.0) 6 moiList=(4.0 5.0 6.0 7.0 8.0 9.0 10.0 11.0 12.0 13.0 14.0 15.0 16.0 17.0 18.0 19.0 20.0 21.0 22.0 23.0 24.0 25.0 26.0 27.0 28.0 29.0) 7 8 start=`date +%s` 9 thread_num=36 10 11 trap "exec 1000>&-;exec 1000<&-;exit 0" 2 12 tmpfifo=$$.fifo 13 mkfifo $tmpfifo 14 exec 1000<>$tmpfifo 15 rm -rf $tmpfifo 16 17 for (( i=0; i<$thread_num; i++ )) 18 do 19 echo >&1000 20 done 21 22 for gamma in ${gammaList[@]} 23 do 24 if [ ! -d "gamma"$gamma ]; then 25 mkdir gamma$gamma 26 fi 27 outfile=`printf "terminal-out-%s" "$gamma"` 28 for moi in ${moiList[@]} 29 do 30 read -u1000 31 { 32 sh auto-run.sh $beta $gamma $moi > $outfile 33 echo >&1000 34 }& 35 done 36 done 37 38 wait 39 end=`date +%s` 40 echo "times: `expr $end - $start`"
- 第 9 行,设置使用进程数.
- 第 11 行,表示脚本在运行过程中,如果接收到 ctrl+c,则则关闭文件描述符 1000 的读写,并正常退出。其中 exec >1000&- 代表关闭对文件描述符 1000 的写,exec <1000&- 代表关闭对文件描述符 1000 的读,trap 用于接受中断命令,2 代表 ctrl+c.
- 12 ~ 14 行,创建一个管道文件 PID.fifo,并与文件描述符 1000 做读写的绑定。如此一来,对文件描述符 1000 的操作就相当于对管道文件的操作,且该文件描述符又有管道的特性,即读写操作必须同时存在,否则会停滞。
- 第 15 行,将管道文件删除(不明白)
- 17 ~ 20 行,向文件描述符 1000 写入相应进程数的空行,用于控制进程数。
- 22 ~ 36 行,主体部分。第 30 行,从文件描述符 1000 读取 1 行,第 31 ~ 34 行,后台执行其中的命令,当第 32 行执行结束后,再向文件描述符 1000 中写入一个空行。结果就是,先读取了文件描述符 1000 中所有的行,读取完后因为没有写入,由于管道性质,该行为被阻塞,因此不会再读取,即不会再执行 read -u1000,但后台若有第 32 行的命令执行结束,便会执行第 33 行的写入命令,写入后 read -u1000 操作便不会阻塞,于是每当跑完一个后台,便可以提交下一个后台。
- 38 ~ 40 行,等待所有命令执行完成,输出耗时。
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现