编辑 - 我修改了脚本,使其可以选择性地显示每个进程的输出。
这里有一个本地批处理解决方案,可可靠地并行运行一系列命令,从未同时启动超过n个进程。
它甚至还具有通过PSEXEC将进程分配到特定CPU或远程机器的内置机制,但我没有测试过该功能。
使其工作的技巧是通过CMD进程启动每个命令,将stdout或未定义的句柄重定向到锁定文件。该进程将保持对该文件的独占锁定,直到终止。无论进程如何终止(正常退出、崩溃、被杀死的进程),锁定都会在其终止时释放。
主脚本可以通过尝试重定向到相同的锁定文件来测试进程是否仍处于活动状态。如果进程仍处于活动状态,则重定向将失败;如果已终止,则成功。
默认情况下,该脚本忽略每个进程的输出。如果以/O
选项作为第一个参数启动,则它会显示每个进程的输出,而不进行交错。
我的演示将进程限制设置为4,并简单地运行一系列不同长度的PING命令。
我已在XP、Vista和Windows 7上进行了测试。
@echo off
setlocal enableDelayedExpansion
:: Display the output of each process if the /O option is used
:: else ignore the output of each process
if /i "%~1" equ "/O" (
set "lockHandle=1"
set "showOutput=1"
) else (
set "lockHandle=1^>nul 9"
set "showOutput="
)
:: The list of commands could come from anywhere such as another file
:: or the output of another command. For this demo I will list the
:: commands within this script - Each command is prefixed with :::
::: ping /n 05 ::1
::: ping /n 20 ::1
::: ping /n 10 ::1
::: ping /n 15 ::1
::: ping /n 07 ::1
::: ping /n 05 ::1
::: ping /n 20 ::1
::: ping /n 10 ::1
::: ping /n 15 ::1
::: ping /n 07 ::1
:: Define the maximum number of parallel processes to run.
:: Each process number can optionally be assigned to a particular server
:: and/or cpu via psexec specs (untested).
set "maxProc=4"
:: Optional - Define CPU targets in terms of PSEXEC specs
:: (everything but the command)
::
:: If a CPU is not defined for a proc, then it will be run on the local machine.
:: I haven't tested this feature, but it seems like it should work.
::
:: set cpu1=psexec \\server1 ...
:: set cpu2=psexec \\server1 ...
:: set cpu3=psexec \\server2 ...
:: etc.
:: For this demo force all CPU specs to undefined (local machine)
for /l %%N in (1 1 %maxProc%) do set "cpu%%N="
:: 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
:: Modify the IN () clause as needed to retrieve the list of commands
set launch=1
for /f "tokens=* delims=:" %%A in ('findstr /b ":::" "%~f0"') do (
if !startCount! lss %maxProc% (
set /a "startCount+=1, nextProc=startCount"
) else (
call :wait
)
set cmd!nextProc!=%%A
if defined showOutput echo -------------------------------------------------------------------------------
echo !time! - proc!nextProc!: starting %%A
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 %lockHandle%^>"%lock%!nextProc!" 2^>^&1 !cpu%%N! %%A
)
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 =%
if defined showOutput echo ===============================================================================
echo !time! - proc%%N: finished !cmd%%N!
if defined showOutput type "%lock%%%N"
if defined launch (
set nextProc=%%N
exit /b
)
set /a "endCount+=1, endProc%%N=1"
)
)
if %endCount% lss %startCount% (
1>nul 2>nul ping /n 2 ::1
goto :wait
)
2>nul del %lock%*
if defined showOutput echo ===============================================================================
echo Thats all folks^^!
12:24:07.52 - proc1: starting ping /n 05 ::1
12:24:07.52 - proc2: starting ping /n 20 ::1
12:24:07.53 - proc3: starting ping /n 10 ::1
12:24:07.54 - proc4: starting ping /n 15 ::1
12:24:11.60 - proc1: finished ping /n 05 ::1
12:24:11.60 - proc1: starting ping /n 07 ::1
12:24:16.66 - proc3: finished ping /n 10 ::1
12:24:16.66 - proc3: starting ping /n 05 ::1
12:24:17.68 - proc1: finished ping /n 07 ::1
12:24:17.68 - proc1: starting ping /n 20 ::1
12:24:20.72 - proc3: finished ping /n 05 ::1
12:24:20.72 - proc3: starting ping /n 10 ::1
12:24:21.75 - proc4: finished ping /n 15 ::1
12:24:21.75 - proc4: starting ping /n 15 ::1
12:24:26.82 - proc2: finished ping /n 20 ::1
12:24:26.82 - proc2: starting ping /n 07 ::1
12:24:29.86 - proc3: finished ping /n 10 ::1
12:24:32.89 - proc2: finished ping /n 07 ::1
12:24:35.92 - proc4: finished ping /n 15 ::1
12:24:36.93 - proc1: finished ping /n 20 ::1
Thats all folks!
如果使用/O
选项运行,则以下是处理输出的结果。
-------------------------------------------------------------------------------
12:24:51.02 - proc1: starting ping /n 05 ::1
-------------------------------------------------------------------------------
12:24:51.02 - proc2: starting ping /n 20 ::1
-------------------------------------------------------------------------------
12:24:51.03 - proc3: starting ping /n 10 ::1
-------------------------------------------------------------------------------
12:24:51.04 - proc4: starting ping /n 15 ::1
===============================================================================
12:24:55.10 - proc1: finished ping /n 05 ::1
Pinging ::1 with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 5, Received = 5, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
-------------------------------------------------------------------------------
12:24:55.10 - proc1: starting ping /n 07 ::1
===============================================================================
12:25:00.17 - proc3: finished ping /n 10 ::1
Pinging ::1 with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 10, Received = 10, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
-------------------------------------------------------------------------------
12:25:00.19 - proc3: starting ping /n 05 ::1
===============================================================================
12:25:01.22 - proc1: finished ping /n 07 ::1
Pinging ::1 with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 7, Received = 7, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
-------------------------------------------------------------------------------
12:25:01.23 - proc1: starting ping /n 20 ::1
===============================================================================
12:25:04.27 - proc3: finished ping /n 05 ::1
Pinging ::1 with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 5, Received = 5, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
-------------------------------------------------------------------------------
12:25:04.28 - proc3: starting ping /n 10 ::1
===============================================================================
12:25:05.30 - proc4: finished ping /n 15 ::1
Pinging ::1 with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 15, Received = 15, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
-------------------------------------------------------------------------------
12:25:05.32 - proc4: starting ping /n 15 ::1
===============================================================================
12:25:10.38 - proc2: finished ping /n 20 ::1
Pinging ::1 with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 20, Received = 20, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
-------------------------------------------------------------------------------
12:25:10.40 - proc2: starting ping /n 07 ::1
===============================================================================
12:25:13.44 - proc3: finished ping /n 10 ::1
Pinging ::1 with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 10, Received = 10, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
===============================================================================
12:25:16.48 - proc2: finished ping /n 07 ::1
Pinging ::1 with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 7, Received = 7, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
===============================================================================
12:25:19.52 - proc4: finished ping /n 15 ::1
Pinging ::1 with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 15, Received = 15, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
===============================================================================
12:25:20.54 - proc1: finished ping /n 20 ::1
Pinging ::1 with 32 bytes of data:
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Reply from ::1: time<1ms
Ping statistics for ::1:
Packets: Sent = 20, Received = 20, Lost = 0 (0% loss),
Approximate round trip times in milli-seconds:
Minimum = 0ms, Maximum = 0ms, Average = 0ms
===============================================================================
Thats all folks!
start "title of the process" "P:\ath\to.exe"
cmd /k start /?
Start菜单至少从WinME开始可用。
祝你好运!
start "title" cmd /k ...
,而不是反过来。否则,直到第一个cmd
结束,您的下一行代码才会运行,而/k
确保这种情况永远不会发生。"进一步阅读"的代码是专门用于在终端而非在线上阅读文档。 - undefinedcmd
窗口(或其他进程),请参见此答案。虽然你可能需要使用一些其他工具和小技巧来创建类似于“进程池”的东西(以便同时运行最多n个实例)。你可以通过使用tasklist /im
并计算已经存在的数量(for
循环或wc
,如果适用)并简单地等待(ping -n 2 ::1 >nul 2>&1
)并再次检查是否可以生成新进程来实现后者。
我为此拼凑了一个小测试批处理:
@echo off
for /l %%i in (1,1,20) do call :loop %%i
goto :eof
:loop
call :checkinstances
if %INSTANCES% LSS 5 (
rem just a dummy program that waits instead of doing useful stuff
rem but suffices for now
echo Starting processing instance for %1
start /min wait.exe 5 sec
goto :eof
)
rem wait a second, can be adjusted with -w (-n 2 because the first ping returns immediately;
rem otherwise just use an address that's unused and -n 1)
echo Waiting for instances to close ...
ping -n 2 ::1 >nul 2>&1
rem jump back to see whether we can spawn a new process now
goto loop
goto :eof
:checkinstances
rem this could probably be done better. But INSTANCES should contain the number of running instances afterwards.
for /f "usebackq" %%t in (`tasklist /fo csv /fi "imagename eq wait.exe"^|find /c /v ""`) do set INSTANCES=%%t
goto :eof
%RANDOM%
),并创建一个帮助程序批处理程序来处理(或生成处理程序),但可以将其窗口标题设置为参数。@echo off
title %1
"%2" "%3"
tasklist /fi "windowtitle eq ..."
)。这应该相当可靠,并且可以防止太多的误报。如果您仍有一些实例正在运行,则搜索cmd.exe
将是一个不好的主意,因为这会限制您的工作进程池。%NUMBER_OF_PROCESSORS%
创建合理的默认值,以确定要生成多少个实例。psexec
远程生成进程(但是,如果您还需要在其他计算机上提供管理员权限并提供密码,这将不是非常可行)。然后,您必须使用进程名称进行过滤。有一个基本的Windows xargs-like-clone,支持-P并行处理选项,网址为http://www.pirosa.co.uk/demo/wxargs/wxargs.html
在Linux下,GNU xargs提供了"-P n"开关来启动n个并行进程。也许xargs的cygwin/mingw版本也支持此功能?
然后您可以使用以下命令:
xargs -P 4 processFile < fileList
不过,没有花哨的多节点进程生成。
start
并不是那么灵活。 - parvusREM ---------------------------------------------------------------------------
REM ---------------------------------------------------------------------------
REM ---------------------------------------------------------------------------
goto:EOF
REM Append this to the END of your batch-file [*.BAT] to get inline "Multi" support
REM "Multi" is a thread-pool emulation helper library for controlling multi-threaded windows batch [*.BAT] files
REM Copyright (c) 2020 Adisak Pochanayon
REM Contact: adisak@gmail.com
REM See Multi_License.txt for details
REM -----------------------------------
:Multi_Setup
call :Multi_SetName %1
if "%2"=="" (
if "%NUMBER_OF_PROCESSORS%"=="" call :Multi_SetLimitToMax
) else (
call :Multi_SetLimit %2
)
goto:EOF
REM -----------------------------------
:Multi_SetName
REM Returns: MULTI_CHILDPROC_WINNAME - name to use for child processes (the window title)
if "%1"=="" (
SET MULTI_CHILDPROC_WINNAME=Multi-CmdProc
) else (
SET MULTI_CHILDPROC_WINNAME=Multi-CmdProc-%1
)
goto:EOF
REM -----------------------------------
REM To Enable Hyperthreading, call Multi_SetHyperThread before calling Multi_Setup or Multi_SetLimitToMax
:Multi_SetHyperThread
REM Parameter 1: (optional)
REM value=1 (or unspecified) - Use Hyperthreading if available
REM value=0 (or other) - Do not use Hyperthreading to compute the max threads
REM Returns: NumberOfCores - number of real CPU cores
REM Returns: MULTI_HAS_HYPERTHREADING - 1 if the CPU has Hyperthreading
REM Returns: MULTI_USE_HYPERTHREADING - 1 if "Multi" should use Hyperthreading
REM Set variable NumberOfCores
if "%NumberOfCores%"=="" (
for /f "tokens=*" %%f in ('wmic cpu get NumberOfCores /value ^| find "="') do set %%f
)
REM Set variable MULTI_HAS_HYPERTHREADING
if "%MULTI_HAS_HYPERTHREADING%"=="" (
if "%NumberOfCores%"=="%NUMBER_OF_PROCESSORS%" (
REM Non-Hyperthreading
SET MULTI_HAS_HYPERTHREADING=0
) else (
REM Hyperthreading
SET MULTI_HAS_HYPERTHREADING=1
)
}
if "%1"=="" (
SET MULTI_USE_HYPERTHREADING=%MULTI_HAS_HYPERTHREADING%
) else (
SET MULTI_USE_HYPERTHREADING=%1
)
REM Set the max threads to the limit (respecting Hyperthreading options)
call :Multi_SetLimitToMax
goto:EOF
REM -----------------------------------
:Multi_SetLimit
REM Parameter 1:
REM value=N - Use N as the number of max threads
REM unspecified - Compute the default number of max threads
REM Returns: MULTI_MAXCHILDREN - the maximum number of child processes to run simultaneously
if "%1"=="" (
if "%MULTI_MAXCHILDREN%"=="" call :Multi_SetLimitToMax
goto:EOF
)
SET /A MULTI_MAXCHILDREN=%1
if %MULTI_MAXCHILDREN% LSS 1 SET MULTI_MAXCHILDREN=1
goto:EOF
REM -----------------------------------
:Multi_SetLimitToMax
REM Parameter 1: (optional)
REM Passed to Multi_SetHyperThread if present
REM Returns: MULTI_MAXCHILDREN - max number of "threads" in pool for "Multi"
if "%1"=="" (
REM Check if Hyperthreading support was initialized
if "%NumberOfCores%"=="" (
call :Multi_SetHyperThread 0
REM Multi_SetHyperThread calls back to this subroutine so exit to prevent recursion
goto:EOF
)
) else (
call :Multi_SetHyperThread %1
REM Multi_SetHyperThread calls back to this subroutine so exit to prevent recursion
goto:EOF
)
if %NUMBER_OF_PROCESSORS% LEQ 3 (
SET MULTI_MAXCHILDREN=1
) else (
if "%NumberOfCores%"=="%NUMBER_OF_PROCESSORS%" (
REM Non-Hyperthreading
SET /A MULTI_MAXCHILDREN=%NUMBER_OF_PROCESSORS%-2
) else if "%MULTI_USE_HYPERTHREADING%"=="1" (
REM Hyperthreading available and used
SET /A MULTI_MAXCHILDREN=%NUMBER_OF_PROCESSORS%/2 - 1
) else (
REM Hyperthreading available but not used
SET /A MULTI_MAXCHILDREN=%NUMBER_OF_PROCESSORS%-2
)
)
goto:EOF
REM -----------------------------------
:Multi_RunWin
if "%MULTI_CHILDPROC_WINNAME%"=="" call :Multi_SetName
call :Multi_WaitChildrenMax
start "%MULTI_CHILDPROC_WINNAME%" /BELOWNORMAL cmd /c %*
goto:EOF
REM -----------------------------------
:Multi_RunWinMin
if "%MULTI_CHILDPROC_WINNAME%"=="" call :Multi_SetName
call :Multi_WaitChildrenMax
start "%MULTI_CHILDPROC_WINNAME%" /MIN /BELOWNORMAL cmd /c %*
goto:EOF
REM -----------------------------------
:Multi_RunSyncMin
REM Use this command to run things that mess with the window title
REM and otherwise would screw up the "Multi" System
start "Multi-Sync" /MIN /WAIT cmd /c %*
goto:EOF
REM -----------------------------------
:Multi_WaitChildrenMax
REM Wait until less than MULTI_MAXCHILDREN children are running
if "%MULTI_MAXCHILDREN%"=="" call :Multi_SetLimitToMax
call :Multi_WaitChildren %MULTI_MAXCHILDREN%
goto:EOF
REM -----------------------------------
:Multi_WaitChildren
SETLOCAL
REM multi_WAITCOUNT is a local variable
SET multi_WAITCOUNT=1
if "%1"=="" GOTO :loop_WaitChildren
SET /A multi_WAITCOUNT=%1
if %multi_WAITCOUNT% LSS 1 set multi_WAITCOUNT=1
:loop_WaitChildren
call :Multi_GetNumChildren
if %MULTI_NUM_CHILDREN% LSS %multi_WAITCOUNT% GOTO :exit_WaitChildren
timeout /t 1 /nobreak > nul
GOTO :loop_WaitChildren
:exit_WaitChildren
ENDLOCAL
goto:EOF
REM -----------------------------------
:Multi_GetNumChildren
REM Returns: MULTI_NUM_CHILDREN - the number of "children" processes (Windows named MULTI_CHILDPROC_WINNAME)
if "%MULTI_CHILDPROC_WINNAME%"=="" call :Multi_SetName
REM MULTI_NUM_CHILDREN should contain the number of
REM running %MULTI_CHILDPROC_WINNAME% instances after this
for /f "usebackq" %%t in (`tasklist /fo csv /fi "WINDOWTITLE eq %MULTI_CHILDPROC_WINNAME%" ^| find /c "cmd"`) do (
SET MULTI_NUM_CHILDREN=%%t
)
goto:EOF
REM -----------------------------------