【bat】大量ファイルを並列処理する方法(startコマンド活用)

バッチ(.bat)は基本的に“直列”で処理が進みますが、start を使えば複数のコマンドを同時に走らせて全体時間を短縮できます。
本記事では start /b による非同期起動、同時実行数の制御(スロット方式)完了待ちログ分離優先度調整 までをテンプレート付きで解説します。

基本:start /b で“非同期”起動する

start は新しいプロセスを起動し、呼び出し元にすぐ戻ります。/b を付けると同じコンソールでバックグラウンド実行され、ウィンドウが増えません。
内部コマンドや複合処理は cmd /c で包むのが定石です。

@echo off
rem 例:3本を並列起動(それぞれ3秒待って終了)
start "" /b cmd /c "echo job1 & timeout /t 3 >nul"
start "" /b cmd /c "echo job2 & timeout /t 3 >nul"
start "" /b cmd /c "echo job3 & timeout /t 3 >nul"

echo 起動だけ行い、呼び出し元は直ちに先へ進みます
rem ※ 直後に集計などをしたい場合は“完了待ち”を入れる(後述)

よくある誤解:start /wait は“直列化”する

start /wait は「起動したプロセスの終了を待つ」オプションで、並列化ではなく 直列化 です。複数本を同時に走らせたい場合は付けません。

同時実行数を制御する“スロット方式”

無制限に起動すると I/O 競合やメモリ不足で逆に遅くなります。次のテンプレートは 最大N本だけ並列 にし、完了したら次を投下します。
仕組みは「ロックファイル(スロット)」を作ってカウントするだけなので、依存ツール不要で堅牢です。

@echo off
setlocal EnableExtensions EnableDelayedExpansion
cd /d "%~dp0"

rem ==== 設定 ====
set "SRC=.\in"                rem 入力ディレクトリ
set "OUT=.\out"               rem 出力ディレクトリ
set "MAXJOBS=4"               rem 同時実行の上限
set "SLOTS=%TEMP%\bat-slots"  rem スロット置き場(ロックファイル群)
mkdir "%OUT%" 2>nul
mkdir "%SLOTS%" 2>nul

rem ==== 本処理(例:*.txt を“擬似変換”して out\ に出力)====
for %%F in ("%SRC%\*.txt") do (
  call :acquire_slot
  set "LOCK=%SLOTS%\%%~nF.lock"
  type nul >"!LOCK!" 2>nul

  rem --- 並列ジョブを起動(/b で非同期、cmd /c で複合処理)---
  start "" /b cmd /c ^
    "echo [START] %%~nxF & ^
     type \"%%~fF\" ^> \"%OUT%\%%~nF.out\" & ^
     timeout /t 2 >nul & ^
     echo [DONE ] %%~nxF & ^
     del /q \"!LOCK!\""
)

rem ==== すべてのジョブが終わるのを待機 ====
call :wait_all
echo すべて完了
exit /b 0

:acquire_slot
  rem 空きスロットができるまで待つ
  :retry
  set /a COUNT=0
  for %%L in ("%SLOTS%\*.lock") do set /a COUNT+=1
  if !COUNT! geq %MAXJOBS% (
    timeout /t 1 >nul
    goto :retry
  )
  exit /b 0

:wait_all
  rem ロックがゼロになるまで待つ
  :waitloop
  dir /b "%SLOTS%\*.lock" >nul 2>&1 || exit /b 0
  timeout /t 1 >nul
  goto :waitloop

ログをファイルごとに分ける(後追い調査しやすく)

並列実行では標準出力が混ざりやすいので、ファイル単位でログを分けます。失敗時は終了コードを記録すると再実行もしやすくなります。

@echo off
set "LOGDIR=%TEMP%\joblogs"
mkdir "%LOGDIR%" 2>nul

for %%F in (".\in\*.txt") do (
  set "NAME=%%~nF"
  start "" /b cmd /c ^
    "(your.exe -i \"%%~fF\" -o \".\out\%%~nF.bin\") ^
     > \"%LOGDIR%\!NAME!.out\" 2> \"%LOGDIR%\!NAME!.err\" ^
     & echo !ERRORLEVEL! > \"%LOGDIR%\!NAME!.code\""
)

CPU・I/O 負荷を抑える:優先度・コア割り当て

重い処理は PC の使用感を損ねがちです。start には /low(低優先度)や /affinity(コア割り当て)があります。

rem 低優先度で実行
start "" /b /low cmd /c "your_heavy_job.bat"

rem 0x3 (=CPU0,1) に限定して実行
start "" /b /affinity 3 cmd /c "your_heavy_job.bat"

ありがちな落とし穴と回避策

  • 相対パスが壊れる: 冒頭で cd /d "%~dp0"
  • 内部コマンドが意図通り動かない: cmd /c "…" で包む。
  • 標準出力が混ざる: ジョブごとにログへリダイレクト。
  • 過剰並列で遅くなる: ストレージやネットワーク帯域に合わせて MAXJOBS を調整。
  • 完了検知が必要: ロックファイル削除+:wait_all で待機。

ミニマムな雛形(コピペ可)

@echo off
setlocal EnableExtensions EnableDelayedExpansion
cd /d "%~dp0"
set "MAXJOBS=4"
set "SLOTS=%TEMP%\slots"
mkdir "%SLOTS%" 2>nul

for %%F in ("work\*.job") do (
  call :acquire
  set "L=%SLOTS%\%%~nF.lock"
  type nul >"!L!"
  start "" /b cmd /c "run.exe -i \"%%~fF\" & del /q \"!L!\""
)

:wait
dir /b "%SLOTS%\*.lock" >nul 2>&1 || goto :end
timeout /t 1 >nul
goto :wait

:acquire
set /a C=0
for %%X in ("%SLOTS%\*.lock") do set /a C+=1
if !C! geq %MAXJOBS% (timeout /t 1 >nul & goto :acquire)
exit /b

:end
echo DONE
endlocal & exit /b 0

まとめ

大量ファイルの並列化は start /b cmd /c で非同期起動し、スロット方式で同時実行数を絞るのがシンプルで堅牢です。
ログはジョブ単位に分け、必要なら /low/affinity で負荷を調整します。上記テンプレートを流用すれば、外部ツールなしでも“実用的な並列処理”を安全に導入できます。