bash 脚本(子进程)的多线程信号量



有没有办法/二进制来做类似信号量的结构?例如。用于在我们循环访问文件目录时运行固定数量的(后台(子进程(此处使用单词"子进程"而不是"线程",因为在我的 bash 命令中使用附加的&来执行"多线程"(但可以接受任何更方便的建议((。

我的实际用例是尝试在 CentOS 7 上使用名为bcp的二进制文件将一组(可变大小(TSV 文件写入远程 MSSQL 服务器数据库,并观察到当运行太多线程时程序似乎存在问题。例如。类似的东西

for filename in $DATAFILES/$TARGET_GLOB; do
if [ ! -f $filename ]; then
echo -e "nFile $filename not found!nExiting..."
exit 255
else
echo -e "nImporting $filename data to $DB/$TABLE"
fi
echo -e "nStarting BCP export threads for $filename"
/opt/mssql-tools/bin/bcp "$TABLE" in "$filename" 
$TO_SERVER_ODBCDSN 
-U $USER -P $PASSWORD 
-d $DB 
$RECOMMEDED_IMPORT_MODE 
-t "t" 
-e ${filename}.bcperror.log &
done
# collect all subprocesses at the end
wait

以不受限制的方式一次为每个文件启动一个新的子进程,似乎使每个子进程崩溃。想看看在循环中添加类似信号量的结构以锁定将要启动的子进程的数量是否有帮助。例如。类似的东西(在这里使用一些非类似 bash 的伪代码(

sem = Semaphore(locks=5)
for filename in $DATAFILES/$TARGET_GLOB; do
if [ ! -f $filename ]; then
echo -e "nFile $filename not found!nExiting..."
exit 255
else
echo -e "nImporting $filename data to $DB/$TABLE"
fi
sem.lock()
<same code from original loop>
sem.unlock()
done
# collect all subprocesses at the end
wait

如果这样的事情是可能的,或者如果这是现有最佳实践解决方案的常见问题(我对 bash 编程很陌生(,建议将不胜感激。

这不是严格等效的,但您可以使用xargs一次启动给定数量的进程:

-P max-procs, --max-procs=max-procs
Run  up  to max-procs processes at a time; the default is 1.  If
max-procs is 0, xargs will run as many processes as possible  at
a  time.   Use the -n option or the -L option with -P; otherwise
chances are that only one exec will be  done.   While  xargs  is
running,  you  can send its process a SIGUSR1 signal to increase
the number of commands to run simultaneously, or  a  SIGUSR2  to
decrease  the  number.   You  cannot decrease it below 1.  xargs
never terminates its commands; when asked to decrease, it merely
waits  for  more  than  one existing command to terminate before
starting another.

像这样:

printf "%sn" $DATAFILES/$TARGET_GLOB |
xargs -d 'n' -I {} --max-procs=5 bash -c '
filename=$1
if [ ! -f $filename ]; then
echo -e "nFile $filename not found!nExiting..."
exit 255
else
echo -e "nImporting $filename data to $DB/$TABLE"
fi
echo -e "nStarting BCP export threads for $filename"
/opt/mssql-tools/bin/bcp "$TABLE" in "$filename" 
$TO_SERVER_ODBCDSN 
-U $USER -P $PASSWORD 
-d $DB 
$RECOMMEDED_IMPORT_MODE 
-t "t" 
-e ${filename}.bcperror.log
' _ {}

您需要事先导出TABLETO_SERVER_ODBCDSNUSERPASSWORDDBRECOMMEDED_IMPORT_MODE变量,以便它们在xargs启动的进程中可用。或者,您可以将此处使用bash -c运行的命令放在单独的脚本中,并将变量放在该脚本中。

根据 @Mark Setchell 的建议,使用 GNU Parallel 将循环(在模拟的 cron 环境中(参见 https://stackoverflow.com/a/2546509/8236733(( 替换为

bcpexport() {
filename=$1
TO_SERVER_ODBCDSN=$2
DB=$3 
TABLE=$4 
USER=$5
PASSWORD=$6
RECOMMEDED_IMPORT_MODE=$7
DELIMITER=$8 # DO NOT use format like "'t'", nested quotes seem to cause hard-to-catch error
<same code from original loop>
}
export -f bcpexport
parallel -j 10 bcpexport 
::: $DATAFILES/$TARGET_GLOB 
::: "$TO_SERVER_ODBCDSN" 
::: $DB 
::: $TABLE 
::: $USER 
::: $PASSWORD 
::: $RECOMMEDED_IMPORT_MODE 
::: $DELIMITER

一次最多运行 10 个线程,其中$DATAFILES/$TARGET_GLOB是一个 glob 字符串,用于返回所需目录中的所有文件。我们想要经历的"$storagedir/tsv/*.tsv"((并添加剩余的固定参数,其中包含该 glob 返回的每个元素作为剩余的并行输入(($TO_SERVER_ODBCDSN变量实际上是"-D -S <some ODBC DSN>",因此需要添加引号作为单个参数传递(。因此,如果$DATAFILES/$TARGET_GLOBglob 返回文件 A、B、C、...,我们最终运行命令

bcpexport A "$TO_SERVER_ODBCDSN" $DB ...
bcpexport B "$TO_SERVER_ODBCDSN" $DB ...
bcpexport C "$TO_SERVER_ODBCDSN" $DB ...
...

并行。使用parallel的另一个好处是

GNU 并行确保命令的输出与按顺序运行命令时获得的输出相同。

Using &

示例代码

#!/bin/bash
xmms2 play &
sleep 5
xmms2 next &
sleep 1
xmms2 stop

最新更新