【问题标题】:Linux concurrency scripting with mutexes使用互斥锁的 Linux 并发脚本
【发布时间】:2012-10-27 10:20:07
【问题描述】:

在我的 Linux 服务器上,我需要同步多个用 BASH 和 PHP 编写的脚本,以便只有其中一个能够启动系统关键工作,即一系列 BASH/PHP 命令,这会造成混乱如果由两个或多个脚本同时执行,事情就会发生。根据我在 C++ 中使用多线程的经验,我熟悉互斥锁的概念,但是如何为在不同进程中运行并且当然不是用 C++ 编写的一堆脚本实现互斥锁呢?

嗯,想到的第一个解决方案是确保每个脚本最初都创建一个“锁定标志”文件,让其他脚本知道该作业已“锁定”,然后在完成后删除该文件工作。但是,正如我所见,文件写入 读取操作需要完全原子 才能让这种方法以 100% 的概率起作用,并且相同的要求将适用于任何其他同步方法。而且我很确定文件写入/读取操作不是原子的,它们至少在所有现有的 Linux/Unix 系统中都不是原子的。

那么同步并发 BASH PHP 脚本最灵活可靠的方法是什么?

【问题讨论】:

  • 我不确定互斥锁将如何帮助您。如果是我,我会简单地依赖锁或 pid 文件 - 即将关键作业的 pid 保存到文件中。在下一次运行任一脚本时,请执行 if(process_exists(readfile(pidfile)))exit; 之类的操作
  • @Christian 好吧,由于 process_existsreadfile(和 writefile)缺乏原子性,并且由于它们没有作为一个原子调用粘合在一起,在我看来,您建议的逻辑仍然会为多个脚本同时开始执行关键代码行的机会留下一个小窗口。
  • 你应该使用 LockFile/flock(我认为)。守护程序脚本 (/init.id/*) 通常这样做......所以他们不太可能做错了。
  • @Christian 你是说/etc/init.d/ 中的脚本吗?无论如何,他们没有给出任何 PHP 示例。
  • 我很抱歉,这是正确的。我认为这些脚本对您的事业很有用,因为您可以尝试查找 bash 代码的 PHP 等效项。嗯,相关系统命令的PHP移植......

标签: php linux shell concurrency


【解决方案1】:

我不是 PHP 程序员,但文档说它提供了一个可移植的 flock 版本供您使用。第一个示例 sn-p 看起来非常接近您想要的。试试这个:

<?php

$fp = fopen("/tmp/lock.txt", "r+");

if (flock($fp, LOCK_EX)) {  // acquire an exclusive lock

    // Do your critical section here, while you hold the lock

    flock($fp, LOCK_UN);    // release the lock
} else {
    echo "Couldn't get the lock!";
}

fclose($fp);

?>

请注意,默认情况下flock 会等待直到它可以获取锁。如果您希望它在另一个程序副本已经在运行的情况下立即退出,您可以使用LOCK_EX | LOCK_NB

使用名称“/tmp/lock.txt”可能是一个安全漏洞(我不想认真思考是否真的如此),因此您可能应该选择一个只能由以下人员写入的目录你的程序。

【讨论】:

  • 我对 PHP 的 flock 有两个担忧。首先是被 PHP 脚本锁定的文件将被视为来自 BASH 脚本的任何其他常规解锁文件。第二个问题是 PHP 的 flock 不能在所有 Linux 文件系统上移植,例如在 NFS 文件系统上不起作用。
  • 你也可以在shell中使用flock;请参阅@KingsIndian 的回答。但是,如果您担心 NFS,您可能需要标准的 mktemp/hardlink/stat dance。这是一个描述,如果你控制 NFS 服务器,还有另一种描述:us3.php.net/manual/en/function.flock.php#82521
  • 在我进行了一些测试之后,您似乎对flock PHP 和 BASH 之间的兼容性是正确的。我刚刚查看了 PHP 的 flock (flock_compat.c) 的源代码,并且看起来,至少 5.3 版本的 PHP 依赖于 fcntl 函数进行文件锁定,正如 here 所述,确实如此也可以在 NFS 上工作。 PHP 开发人员甚至在flock_compat.h 文件中提到了这种NFS 支持。
  • @DesmondHume,关于将LOCK_NB 添加到示例代码的建议编辑:flock 即使在阻塞模式下也可能失败。阅读flock(2),我认为最可能的原因是“内核用于分配锁记录的内存不足”,但可能还有其他方法。即使在阻塞模式下,最好检查并确保您确实获得了锁。
【解决方案2】:

您可以使用flock 以原子方式锁定您的标志文件。 -e 选项是获取排他锁。

来自手册页:

默认情况下,如果不能立即获得锁,flock会等待 直到锁可用。

因此,如果您的所有 bash/php 脚本都尝试以独占方式锁定文件,则只有一个可以成功获取它,其余脚本将等待锁定。

如果您不想等待,请使用-w 超时。

【讨论】:

  • @DesmondHume 我从未做过 PHP 编程。如果您想知道如何使用它,手册页本身包含一个 shell 脚本中的小示例。无论如何,我认为你的反对票是不合理的。
【解决方案3】:

Bash 中基于fuser 的锁(它保证没有两个进程同时访问受保护的资源,但即使没有进程访问该资源也可能导致负锁定尝试,但几乎不可能):

#!/bin/bash
set -eu
function mutex {
 local file=$1 pid pids
 exec 8>>"$file"
 { pids=$(/sbin/fuser -f "$file"); } 2>&- 9>&-
 for pid in $pids; do
   [[ $pid = $$ ]] && continue
   exec 8>&-
   return 1 # locked by other pid
 done
}

【讨论】:

  • 那么用exec 8&gt;&gt;"$file"创建文件描述符是原子操作吗?
  • 通过打开“lock”文件,进程成为文件的 N+1 打开者,调用 fuser 下方的 for 循环检查是否有任何其他进程在调用时打开了相同的文件到融合器,所以打开描述符是否是原子的并不重要
  • 如果我错了,请纠正我,但是,即使exec 8&gt;&gt;"$file" 是原子的,使用您的代码,脚本仍然可以在另一个脚本打开锁定文件后一纳秒内打开锁定文件,这将导致两个脚本在fuser 告诉它们锁定文件已被另一个进程(即并发脚本)打开后相互抵消。没有?
  • @DesmondHume - 你是对的,它过于悲观,这对于大多数脚本级任务来说都很好。如果您需要更精确且不那么悲观(而不是在 bash/php 的上下文中进行理论考虑),您可能不应该使用此代码片段。
  • .. “悲观”是指“乐观”;)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-05-23
  • 1970-01-01
  • 1970-01-01
  • 2014-10-09
  • 1970-01-01
  • 2020-06-03
  • 1970-01-01
相关资源
最近更新 更多