我有一个文本文件,该文件内部是一个数字,并且我在 ksh 中有一个 script.sh 。该脚本读取文件并获取数字,然后将数字加 1 并覆盖文件中的新数字,然后休眠一段时间并重复该过程,直到数字等于 120。
我想让这个脚本同时运行n次,我该怎么做?
但随后我将有 n 个进程尝试编辑 file.txt,并且为了只有一个进程进行编辑,当它完成(休眠)时,第二个进程可以编辑,依此类推...
也许你会告诉我,我必须使用锁文件,但不幸的是我不能使用它,所以我必须找到另一种方法来模拟信号量。
有任何想法吗?
谢谢。
答案1
我更喜欢从外部控制数字的操作,只需调用脚本并将当前数字作为参数传递给它:
n=10
read nr < number.file
seq $((nr+1)) 120 | xargs -n 1 -P $n script.sh
脚本本身将简化为如下所示:
#!/bin/ksh
number=$1
echo "Do job with/for number $number"
echo $number > number.file
sleep 10
当然,如果任务的持续时间可能会有所不同,那么最好在写入之前检查数字文件的当前内容,以免覆盖更大的文件。但只有当任务的连续性需要支持某种类型的恢复时,这一点才重要。
答案2
在没有锁定文件的情况下执行此操作似乎需要一些奇怪的东西,因为锁定文件(更好的是锁定目录,因为目录创建应该是原子的)是此问题的标准且可行的解决方案。我还没有完全按照你想做的做,但这里有一些我想到的想法:
您可以编写一个小型 C 程序来检查 SysV 信号量的状态。以man semget
或开头man semop
。这将是乏味且奇怪的。
您可以使用 Oraclesqlplus
和 PL/SQL 块来执行以下操作:
lock table table_name in exclusive mode
然后再次调用 来sqlplus
释放锁。我以前做过类似的事情,你必须非常小心,不要让进程等待,并释放锁。我也可能只对桌面上的工作感兴趣,所以我只打过一次电话sqlplus
。
在左侧字段中,您可以使用命名管道作为互斥锁。你真的必须尝试一下。
如果您可以加载内核模块,也许内核模块可以使用/proc
虚拟文件来充当互斥体或信号量。 IBM DeveloperWorks 有一篇文章在创建文件的可加载模块上/proc
。
也许你可以实施德克尔算法使用文件或命名管道中的值。
在回顾了我写的内容之后,我不太确定除了 C 程序之外的任何这些semop()
是否真的有效。它们都需要大量的实验。
答案3
假设:
- 您的所有脚本实例都在同一台计算机上运行。
- 您的脚本可以在一个已知的目录中写入,并且其他程序不会使用该目录。该目录位于“普通”文件系统(特别是非 NFS)上。
set -C; (: >foo) 2>/dev/null
您可以使用文件创建( )、重命名(mv
)和删除( )等原子操作rm
来处理锁定。
要通知进程,您可以向其发送信号;然而,定位目标进程是有问题的:如果将进程 ID 存储在某处,则无法确定这些 ID 是否仍然有效,它们可能已被不相关的进程重用。同步两个进程的一种方法是在管道上写入一个字节;读者将阻塞直到作家出现,反之亦然。
首先,设置目录。创建一个名为 的文件lock
和一个名为 的命名管道pipe
。
if ! [ -d /script-locking-directory ]; then
# The directory doesn't exist, create and populate it
{
mkdir /script-locking-directory-$$ &&
mkfifo /script-locking-directory-$$/pipe &&
touch /script-locking-directory-$$/lock &&
mv /script-locking-directory-$$ /script-locking-directory
} || {
# An error happened, so clean up
err=$?
rm -r /script-locking-directory-$$
# Exit, unless another instance of the script created the directory
# at the same time as us
if ! [ -d /script-locking-directory ]; then exit $?; fi
}
fi
我们将通过重命名文件来实现锁定lock
。此外,我们将使用一个简单的方案来通知锁上的所有等待者:将一个字节回显到管道,并让所有等待者通过从该管道读取数据来等待。这是一个简单的方案。
take_lock () {
while ! mv lock lock.held 2>/dev/null; do
read <pipe # wait for a write on the pipe
done
}
release_lock () {
mv lock.held lock
read <pipe & # make sure there is a reader on the pipe so we don't block
echo >pipe # notify all readers
}
该方案唤醒所有服务员,这可能效率低下,但是除非存在大量争用(即同时有很多服务员),否则这不会成为问题。
上面代码的主要问题是,如果锁持有者死亡,锁将不会被释放。我们怎样才能发现这种情况呢?由于PID重用,我们不能只去寻找像锁这样的进程。我们可以做的是打开脚本中的锁定文件,并在新的脚本实例启动时检查锁定文件是否打开。
break_lock () {
if ! [ -e "lock.held" ]; then return 1; fi
if [ -n "$(fuser lock.held)" ]; then return 1; fi
# If we get this far, the lock holder died
if mv lock.held lock.breaking.$$ 2>/dev/null; then
# Check that someone else didn't break the lock and take it just now
if [ -n "$(fuser lock.breaking.$$)" ]; then
mv lock.breaking.$$ lock.held
return 0
fi
mv lock.breaking.$$ lock
fi
return 0 # whether we did break a lock or not, try taking it again
}
take_lock () {
while ! mv lock lock.taking.$$ 2>/dev/null; do
if break_lock; then continue; fi
read <pipe # wait for a write on the pipe
done
exec 9<lock.taking.$$
mv lock.taking.$$ lock.held
}
release_lock () {
# lock.held might not exist if someone else is trying to break our lock.
# So we try in a loop.
while ! mv lock.held lock.releasing.$$ 2>/dev/null; do :; done
exec 9<&-
mv lock.releasing.$$ lock
read <pipe & # make sure there is a reader on the pipe so we don't block
echo >pipe # notify all readers
}
如果锁持有者在另一个实例处于内部时死亡,则此实现仍然可能死锁take_lock
:锁将保持持有状态,直到第三个实例启动。我还假设脚本不会在take_lock
或内消失release_lock
。
警告:上面的代码是我直接在浏览器中编写的,我没有测试它(更不用说证明它是正确的)。