批处理文件多任务处理与父级共享变量

时间:2015-10-31 15:00:02

标签: windows batch-file scope parent-child multitasking

我试图在x265中对视频文件进行批量编码,并希望提出一个可以自动完成该过程的批处理文件。为了加快速度,我发现有2个线程调用3个ffmpeg实例导致理想的编码时间,但是我昨天整天都试图找到一种方法来获取一个调用3个实例的批处理文件然后在完成时调用新的。到目前为止,这就是我所在的地方:

PARENT BATCH

@echo off
Setlocal EnableDelayedExpansion
SET /A COUNT=0
for %%a in (*.mkv) do (
    CALL :CHECK
    SET /A COUNT+=1
    START CALL "child.bat" "%%a"
    )
EXIT /B 0

:CHECK
IF !COUNT! EQU 3 (
    TIMEOUT /T 5
    GOTO :CHECK
)
EXIT /B 0

儿童批次

ffmpeg <COMMAND LINE ARGS>
SET /A COUNT-=1
EXIT /B 0

我有两个问题。 1)COUNT变量未在父进程中更新,并且在子进程完成时它永远不会生成新实例。 2)孩子的过程并没有干净地退出。它会在DOS提示符下打开一个单独的cmd.exe窗口。

有什么想法吗?

编辑:替换嵌套的GOTO以防止FOR循环破坏

下面的解决方法

Setlocal EnableDelayedExpansion
SET /A COUNT=0
for %%a in (*.mkv) do (
    IF !COUNT! EQU 3 CALL :CHECK
    SET /A COUNT+=1
    START CALL "child.bat" "%%a"
    )
EXIT /B 0

:CHECK
IF EXIST DONE.TXT (
    SET /A COUNT-=1
    DEL DONE.TXT
    EXIT /B 0
    ) ELSE (
    TIMEOUT /T 5
    GOTO :CHECK
    )
EXIT /B 0

儿童批次

ffmpeg <COMMAND LINE ARGS>

:DONE
IF EXIST DONE.TXT (
    TIMEOUT /T 1
    GOTO :DONE
)
echo 1 >> DONE.TXT
EXIT 0

2 个答案:

答案 0 :(得分:2)

关于你陈述的问题:

1)子进程无法修改父进程中的环境变量。您将需要一种不同的机制来检测孩子何时终止。此外,正如Squashman在他的评论中所述,循环中的GOTO将中断(终止)循环,这就是为什么在前3个之后没有新的子进程被启动的原因。

2)因为您使用EXIT / B,您的子窗口不会终止。改为使用EXIT,窗口将关闭。

在你有一个可行的解决方案之前,你还有很长的路要走; - )

也许最大的障碍是检测子进程何时终止。

我知道3种策略:

1)使用TASKLIST和FIND / C来计算当前正在运行的ffmpeg进程的数量。这可能是最简单的解决方案,但它无法区分脚本启动的进程与可能由其他机制启动的进程。

2)使用文件作为信号。为每个进程创建一个空文件,然后在进程完成后,让它删除该文件。您的主脚本可以通过查找文件来监视哪些进程处于活动状态。这也很简单,但如果您的某个进程在删除文件之前崩溃,则表现不佳。这会使您的系统处于不健康的状态。

3)我最喜欢的是使用锁文件。每个子进程都通过重定向锁定文件,当进程终止时(崩溃,正常退出,无关紧要),然后释放锁。主进程可以尝试锁定相同的文件。它知道如果锁定成功则孩子已经终止,否则孩子仍在运行。这个策略是最复杂的,它使用了晦涩的语法,但我发现它非常有效。

我已经在Parallel execution of shell processes使用选项3)实施了一个解决方案。以下是适合您情况的代码的改编/简化。

我使用START / B在父窗口中启动每个子进程,然后将所有输出重定向到锁定文件。完成后,我输入输出文件,以便您可以看到发生了什么。我还列出了每个子进程的开始和结束时间。

您只需调整3个顶级环境变量即可满足您的需求。剩下的应该是好的。但是,如果任何文件名包含!字符,则写入的代码将失败。可以通过更多工作来消除此限制。

脚本中有大量文档。 %= COMMENT =%语法是在不使用REM的情况下在循环中安全嵌入注释的一种方法。

@echo off
setlocal enableDelayedExpansion

:: Define the command that will be run to obtain the list of files to process
set listCmd=dir /b /a-d *.mkv

:: Define the command to run for each file, where "%%F" is an iterated file name from the list
::   something like YOUR_COMMAND -i "%%F"
set runCmd=ffmpeg  [** command arguments go here **]

:: Define the maximum number of parallel processes to run.
set "maxProc=3"

::---------------------------------------------------------------------------------
:: The remainder of the code should remain constant
::

:: Get a unique base lock name for this particular instantiation.
:: Incorporate a timestamp from WMIC if possible, but don't fail if
:: WMIC not available. Also incorporate a random number.
  set "lock="
  for /f "skip=1 delims=-+ " %%T in ('2^>nul wmic os get localdatetime') do (
    set "lock=%%T"
    goto :break
  )
  :break
  set "lock=%temp%\lock%lock%_%random%_"

:: Initialize the counters
  set /a "startCount=0, endCount=0"

:: Clear any existing end flags
  for /l %%N in (1 1 %maxProc%) do set "endProc%%N="

:: Launch the commands in a loop
  set launch=1
  for /f "tokens=* delims=:" %%F in ('%listCmd%') do (
    if !startCount! lss %maxProc% (
      set /a "startCount+=1, nextProc=startCount"
    ) else (
      call :wait
    )
    set cmd!nextProc!=%runCmd%
    echo -------------------------------------------------------------------------------
    echo !time! - proc!nextProc!: starting %runCmd%
    2>nul del %lock%!nextProc!
    %= Redirect the lock handle to the lock file. The CMD process will     =%
    %= maintain an exclusive lock on the lock file until the process ends. =%
    start /b "" cmd /c >"%lock%!nextProc!" 2^>^&1 %runCmd%
  )
  set "launch="

:wait
:: Wait for procs to finish in a loop
:: If still launching then return as soon as a proc ends
:: else wait for all procs to finish
  :: redirect stderr to null to suppress any error message if redirection
  :: within the loop fails.
  for /l %%N in (1 1 %startCount%) do 2>nul (
    %= Redirect an unused file handle to the lock file. If the process is    =%
    %= still running then redirection will fail and the IF body will not run =%
    if not defined endProc%%N if exist "%lock%%%N" 9>>"%lock%%%N" (
      %= Made it inside the IF body so the process must have finished =%
      echo ===============================================================================
      echo !time! - proc%%N: finished !cmd%%N!
      type "%lock%%%N"
      if defined launch (
        set nextProc=%%N
        exit /b
      )
      set /a "endCount+=1, endProc%%N=1"
    )
  )
  if %endCount% lss %startCount% (
    timeout /t 1 /nobreak >nul
    goto :wait
  )

2>nul del %lock%*
echo ===============================================================================
echo Thats all folks

答案 1 :(得分:0)

带有随机窗口标题的简便解决方案(基于user2956477的想法):

set windowtitle=Worker_%random%%random%%random%
start /min cmd /c "title %windowtitle% & dosomething.bat file1"
start /min cmd /c "title %windowtitle% & dosomething.bat file2"
start /min cmd /c "title %windowtitle% & dosomethingelse.exe"

timeout 3 >nul
:LOOP
tasklist /V /FI "imagename eq cmd.exe" /FI "windowtitle eq %windowtitle%*" | findstr %windowtitle% >nul
if "%errorlevel%"=="0" timeout 1 >nul && goto LOOP