CEBImagery.com
PHPにて重い処理を行おうとすると、ユーザへのレスポンスが遅くなります。
なので、ユーザへレスポンスする必要の無い処理。例えばメールの送信や裏で行うクエリ等は
レスポンスを待たずに、次の処理へ進んでもらいたいですね。
そんな時に役立つ、重い処理を別スレッドにて起動させて、
メインスレッドでは、そのまま次の処理へ移行させる方法のご紹介です
因みに、Linux/Unix環境用を想定しています、
Windowsサーバでは動かないかも知れませんので、注意して下さい。
別スレッド起動にはexec()関数でのコマンドに秘密が!
重い処理を、バックグラウンドで走らせたい。レスポンスに関係無い処理の為に速度を落としたくない。
そんな時は、exec()を使用して別スレッドにて別のPHPファイルを起動させます。
echo "開始"; // 完全に特定のメアドへの攻撃みたいですが、例えばこんな感じの重い処理 for($i = 1; $i <= 10000000000000000000; $i++) { mb_send_mail( "メールアドレス", "件名", "内容", "ヘッダ", "パラメータ" ); } echo "終了";
上記の様に、例えばメルマガの配信をセットしたりすると、
メルマガの全ての送信を待たないとレスポンスは帰って来ませんので
次の処理を行う事が出来ません。
そこで、上記の様な重い処理を別ファイルにして、exec()にて呼び出します
exec("nohup php -c '' '起動PHPファイルパス' '引数ARGS' > /dev/null &");
後半の『 > /dev/null &』で返却値を捨てていますので、レスポンスを待たずに
次の処理へ進める事が出来ます。
また、前半の『nohup』では、バックグラウンドにて処理中のタスクがログアウトしてしまったり、
ハングアップしてしまっても、処理を続けるように指定します。
echo "開始"; // 重い処理が書かれたmail.phpを別スレッドにて起動 exec("nohup php -c '' 'mail.php' > /dev/null &"); echo "終了";
// 完全に特定のメアドへの攻撃みたいですが、例えばこんな感じの重い処理 for($i = 1; $i <= 10000000000000000000; $i++) { mb_send_mail( "メールアドレス", "件名", "内容", "ヘッダ", "パラメータ" ); }
これで、『開始』と表示された後に、ループ処理の完了を待たずに『終了』と表示されるかと思います。
ループが完了したら、完了結果と共に破棄されます。
別スレッドにて起動したスクリプトに対して引数を渡そう!
また、ただ起動させるだけではデータの共有が出来なくて、とても扱いにくいです。
しかし、コマンドラインに引数を含める事によって、実行先ファイルへも
値を渡す事が出来ます。
これで、別スレッドにて動かす事の出来る関数の様に使用出来ますね。
引数は、実行ファイル名の後に半角スペース区切りで追加して行きます
exec("nohup php -c '' 'mail.php' '引数1' '引数2' '引数3' > /dev/null &");
コマンドで渡されてきた引数は、$argvという定数を使用します。
$argvには、引数で渡されて来た値が順番に配列として格納されています。
ですので、取り出すときは
// 引数1 $arg1 = $argv[1]; // 引数2 $arg2 = $argv[2]; // 引数3 $arg3 = $argv[3];
の様に取り出します。
通常、配列といえばインデックスを0から読んでいきますが、
$argvは$argv[1]からの値が引数で渡されてきた値ですので、
注意しましょう。
では$argv[0]には何が格納されているのか?
それは、スクリプトの実行に使う名前が格納されています。
今回の例ですと、実行ファイルまでのパスが格納されているかと思います。
中身を一気に確認したい時は、ログに出力して確認してみてください。
単純にechoやvar_dumpしたとしても、別スレッドでの実行の上、
『 > /dev/null &』で返却を捨てているのでブラウザには何も表示されません。
必ず、ログファイルへの書き込みにてデバッグを行いましょう。
因みに、単純にログファイルへ書き込むには下記の様にします
// 引数1 $arg1 = $argv[1]; // 引数2 $arg2 = $argv[2]; // 引数3 $arg3 = $argv[3]; // ログ出力 $fp = fopen("logs/arg".date('Ymd').".log", "a"); fprintf($fp, "-------------------------------------\n"); fprintf($fp, date('Y/m/d H:i:s')."\n"); fprintf($fp, "-------------------------------------\n"); fprintf($fp, $arg1."\n"); fprintf($fp, $arg2."\n"); fprintf($fp, $arg3."\n"); fclose($fp);
尚、$argvの他にも、$argcという定数も用意されており、
$argvは引数の値を保持するのに対して、$argcは渡されてきた引数の数を保持しています。
しかし、$argvにはスクリプトファイル名が常に入っていますので、
最小値は必ず1です。
count($argv)とした時と変わらないですね。
余り使い所が無い定数に思えます。
ちなみに、どうしても$argvに値が入って来ない時は、
php.iniの設定でregister_argc_argvの設定を確認しましょう
register_argc_argvの設定が無効になっていると、
$argvと$argcを使用する事が出来ません。
PHPを別スレッドで動かす際の注意点
これで重い処理はユーザへのストレス無く、バックグランドで実行させる事が出来ますね。
もしかしたら、使い方によってはイベント駆動の新しい処理も思いつくかも知れません。
しかし結果完了を待たないというのは、何かあっても後戻り出来ない上に、
チェック機構が難しいので、使い所には注意が必要です。
例えば、上記の例ではメルマガを例えに出しましたが、
メールの送信に失敗した時の記述を行っていません。
これではメール送信のリクエストをセットしたとしても、
メールが送れたのがわからなければ、不安で誰も使ってはくれません。
こういった場合は、実行結果をログに出力して
後程確認出来る様にしたり、実行に失敗したリストを別のDB等に格納して、
管理画面等から確認、再送等のオペレーションを行えるように実装する。
等の処置が必要かと思います。
少し手間ですが、ユーザにとっては送信ボタンを押して固まるよりも、
『送信を予約しまし』の様にレスポンスを早く返して上げた方が、喜ばれるかと思います。
完全に処理を終了した時は、ウェブマスターへメール通知するのも良いでしょう。
後はプログラマなりシステムエンジニアの設計次第で、いくらでも使用方法は見えてくると思います。
また、オブジェクト指向的にも、関連性を緩くする意味でも、
使い所は沢山有るかと思います。
『ここは使い所だ!』『こう対処しよう!』『連携させると強力だ!』
一つでも発見したら、PHPでの実装の仕方が変わるかと思います。
処理が重いな!と感じた時は、是非とも今回の実装案の採用を検討して下さい。