【问题标题】:Synchronize shell script execution同步 shell 脚本执行
【发布时间】:2010-12-01 05:41:13
【问题描述】:

shell script 的修改版本将音频文​​件从 FLAC 转换为 MP3 格式。该计算机具有四核 CPU。该脚本使用以下方式运行:

./flac2mp3.sh $(find flac -type f)

这会将flac 目录中的FLAC 文件(文件名中没有空格)转换为mp3 目录中的MP3 文件(与flac 处于同一级别)。如果目标 MP3 文件已存在,则脚本将跳过该文件。

问题是有时脚本的两个实例几乎同时检查是否存在同一个 MP3 文件,从而导致 MP3 文件损坏。

您将如何多次运行脚本(即每个内核一次),而不必在每个命令行上指定不同的文件集,并且不覆盖工作?

更新 - 最小竞争条件

脚本使用以下锁定机制:

  # Convert FLAC to MP3 using tags from flac file.
  #
  if [ ! -e $FLAC.lock ]; then
    touch $FLAC.lock
    flac -dc "$FLAC" | lame${lame_opts} \
      --tt "$TITLE" \
      --tn "$TRACKNUMBER" \
      --tg "$GENRE" \
      --ty "$DATE" \
      --ta "$ARTIST" \
      --tl "$ALBUM" \
      --add-id3v2 \
      - "$MP3"
    rm $FLAC.lock
  fi;

但是,这仍然存在竞争条件。

【问题讨论】:

    标签: linux bash shell multiprocessing race-condition


    【解决方案1】:

    “lockfile”命令提供了您尝试对没有竞争条件的 shell 脚本执行的操作。该命令由 procmail 人员专门为此目的编写,可在大多数 BSD/Linux 系统上使用(因为 procmail 适用于大多数环境)。

    你的测试变成这样:

    lockfile -r 3 $FLAC.lock
    if test $? -eq 0 ; then
      flac -dc "$FLAC" | lame${lame_opts} \
        --tt "$TITLE" \
        --tn "$TRACKNUMBER" \
        --tg "$GENRE" \
        --ty "$DATE" \
        --ta "$ARTIST" \
        --tl "$ALBUM" \
        --add-id3v2 \
        - "$MP3"
    fi
    rm -f $FLAC.lock
    

    或者,您可以让 lockfile 无限期地重试,这样您就不需要测试返回码,而是可以测试输出文件以确定是否运行 flac。

    【讨论】:

    【解决方案2】:

    如果您没有 lockfile 并且无法安装它(在其任何版本中 - 有几种实现),那么健壮且可移植的原子互斥锁是 mkdir

    如果您尝试创建的目录已经存在,mkdir 将失败,因此您可以检查一下;创建成功后,您可以保证没有其他协作进程与您的代码同时处于临界区。

    if mkdir "$FLAC.lockdir"; then
        # you now have the exclusive lock
        : critical section
        : code goes here
        rmdir "$FLAC.lockdir"
    else
        : nothing? to skip this file
        # or maybe sleep 1 and loop back and try again
    fi
    

    为了完整起见,如果您在一组可靠地提供flock 的平台上并且需要lockfile 的高性能替代品,也可以寻找flock

    【讨论】:

      【解决方案3】:

      您可以对正在处理的 FLAC 文件实施锁定。比如:

      if (not flac locked)
        lock flac
        do work
      else
        continue to next flac
      

      【讨论】:

      • 这个 Yahoo Answer 似乎对如何在 shell 脚本中执行此操作有一个好主意。 answers.yahoo.com/question/index?qid=20061011215658AAbuBfB
      • 正如他所展示的那样。您需要修改 shell 脚本以包含此逻辑。
      • 这与拜伦的回答存在相同的竞争条件。您需要检查并锁定单个原子操作以避免它。
      【解决方案4】:

      将输出发送到具有唯一名称的临时文件,然后将文件重命名为所需的名称。

      flac -dc "$FLAC" | lame${lame_opts} \
            --tt "$TITLE" \
            --tn "$TRACKNUMBER" \
            --tg "$GENRE" \
            --ty "$DATE" \
            --ta "$ARTIST" \
            --tl "$ALBUM" \
            --add-id3v2 \
            - "$MP3.$$"
      mv "$MP3.$$" "$MP3"
      

      如果竞争条件每隔一段时间就会通过您的文件锁定系统泄漏,最终输出仍将是一个进程的结果。

      【讨论】:

      • 这显然避免了在竞争情况下令人不快的数据修改,但如果两个进程对同一个文件进行编码然后或多或少地覆盖其结果,仍然不能防止大量冗余工作立即。
      【解决方案5】:

      要锁定文件进程,您可以创建一个具有相同名称且扩展名为 .lock 的文件。

      在开始编码之前检查 .lock 文件的存在,并可选择确保 lockfile 的日期不是太旧(以防进程终止)。如果不存在,则在编码开始前创建,编码完成后删除。

      您也可以对文件进行群聚,但这仅在调用flock() 并写入文件然后关闭和解锁的c 中才真正有效。对于 shell 脚本,您可能正在调用另一个实用程序来写入文件。

      【讨论】:

      • 不,这有一个竞争条件——如果文件出现在您检查和(尝试)创建它之间,则它无法达到预期目的。您需要一步完成原子检查和创建。
      【解决方案6】:

      写一个 Makefile 怎么样?

      ALL_FLAC=$(wildcard *.flac)
      ALL_MP3=$(patsubst %.flac, %.mp3, $(ALL_FLAC)
      all: $(ALL_MP3)
      %.mp3: %.flac
              $(FLAC) ...
      

      那就做吧

      $ make -j4 all
      

      【讨论】:

        【解决方案7】:

        在 bash 中可以设置 noclobber 选项以避免文件覆盖。

        帮助集 | egrep 'noclobber|-C'

        【讨论】:

          【解决方案8】:

          使用FLOM (Free LOck Manager) 之类的工具并简单地序列化您的命令,如下所示:

          flom -- flac ....
          

          【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-07-23
          • 1970-01-01
          • 1970-01-01
          • 2014-03-26
          • 1970-01-01
          • 2013-06-13
          • 2014-05-15
          • 2011-05-21
          相关资源
          最近更新 更多