Параллельное выполнение оболочечных процессов

Имеется ли инструмент для параллельного выполнения нескольких процессов в пакетном файле Windows? Я нашел несколько интересных инструментов для Linux (parallel и PPSS), однако мне нужен инструмент для платформ Windows.

Бонус: было бы замечательно, если бы инструмент также позволял легко распределять процессы между несколькими машинами, удаленно запуская процессы a la PsExec.

Пример: мне бы хотелось, чтобы в следующем для цикла

for %F in (*.*) do processFile.exe %F

ограниченное количество экземпляров processFile.exe выполняется параллельно, чтобы использовать преимущества многоядерных процессоров.

Ответ 1

GNU xargs под Linux имеет переключатель "-P n" для запуска параллельных процессов "n".

Возможно, cygwin/mingw build of xargs также поддерживает это?

Затем вы можете использовать:

xargs -P 4 processFile < fileList

Никаких причудливых многострочных node процессов нерест.

Ответ 2

Изменить - я изменил скрипт, чтобы при желании отображать вывод каждого процесса

Вот нативное пакетное решение, которое надежно выполняет список команд параллельно, никогда не запуская более чем n процессов одновременно.

В него даже встроен механизм для распределения процессов по конкретным процессорам или удаленным компьютерам через PSEXEC, но я не тестировал эту функцию.

Хитрость, чтобы сделать эту работу, состоит в том, чтобы запустить каждую команду через процесс CMD, который перенаправляет либо стандартный вывод, либо неопределенный дескриптор в файл блокировки. Процесс будет поддерживать эксклюзивную блокировку файла, пока не завершится. Не имеет значения, как завершается процесс (обычный выход, сбой, остановленный процесс), блокировка будет снята, как только она это сделает.

Главный сценарий может проверить, если процесс все еще активен, пытаясь перенаправить на тот же файл блокировки. Перенаправление не будет выполнено, если процесс все еще активен, и завершится успешно, если он завершился.

По умолчанию скрипт игнорирует выходные данные каждого процесса. Если запустить с параметром /O в качестве 1-го параметра, то он отображает результаты каждого процесса без чередования.

Моя демонстрация устанавливает предел процесса равным 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

Ответ 3

Попробуйте start:

start "title of the process" "P:\ath\to.exe"

Открывает новое окно с данным заголовком и исполняет файл BAT, CMD или EXE. Вы также можете установить приоритет, установить ту же среду и т.д.

Файлы, которые не выполняются, открываются с помощью связанной программы.

Дальнейшее чтение: Пуск → Выполнить

cmd /k start /?

Начало доступно, по крайней мере, с WinME.

Удачи!

Ответ 4

Похоже, вы хотите использовать Powershell 2. Однако вы можете создавать новые окна cmd (или другие процессы) с помощью start, см. также this ответ. Хотя вам, вероятно, придется использовать некоторые другие инструменты и немного обмануть, чтобы создать что-то вроде "пула процессов" (чтобы иметь только максимум n экземпляров, запущенных за раз). Вы можете достичь последнего, используя tasklist /im и подсчитывая, сколько из них уже существует (for loop или 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 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 для запуска процессов удаленно (но не будет очень жизнеспособным, так как вы должны иметь права администратора на другом компьютере, а также предоставить пароль в пакете). Однако вам придется использовать имена процессов для фильтрации.