プロが教える店舗&オフィスのセキュリティ対策術

CGIから複数の別プログラムを同時に呼び出す

Web上にある複数の画像を wgetで自サーバー内に取得してから、それにリンクした形のHTMLソースを吐き出すプログラムがあります。
ところが、複数の画像はループで順に取りに行っているものですから、全部取り込んでからHTMLを表示させるのに非常に時間がかかってしまいます。

そこで、wgetでファイルを取りに行くサブルーチン部分を独立させたプログラムを別に作り、接続先などのプロパティを渡した上、それぞれ同時に複数立ち上げて実行させようと思うのですが、可能なのでしょうか?

system()関数だと一応別のプログラムを呼び出せるようですが、結局親プログラムは呼び出した一つの子プログラムの終了まで待って次に行くみたいなので意味ないかと・・・。

どなたかご教授頂ければ幸いです。

A 回答 (7件)

Parallel::ForkManagerというモジュールがあります。


やっていることはただのforkですけどね。

#!/usr/bin/perl
use strict;
use Parallel::ForkManager;

my @url = qw/url_a url_b/;
my $pm = Parallel::ForkManager->new(20); #同時実行の数

for(@url){
  $pm->start and next;
  system("wget $_ >& /dev/null");
  $pm->finish;
}

$pm->wait_all_children;


もし速度が重要なら、画像取得後にHTMLを出力するのではなく、先に出力してからJavaScriptで動的に表示したほうがいいですよ。

参考URL:http://search.cpan.org/~dlux/Parallel-ForkManage …

この回答への補足

モジュールがあるんですね。
これは楽ちんでいいです。
ありがとうございました。

> もし速度が重要なら、画像取得後にHTMLを出力するのではなく、先に出力してから
> JavaScriptで動的に表示したほうがいいですよ。

画像取得後に <img src="abc.jpg"> と出力するよりも、
先に
<SCRIPT language="JavaScript">
<!--
document.write("<img src=\"abc.jpg\">");
//-->
</SCRIPT>
みたいに書いたHTMLを出力しちゃったほうがいいということでしょうか?

Javascriptがよく分かっていないのですが、こうするとabc.jpgを取得し終わる前に
ブラウザにHTMLソースを送ってしまっても大丈夫てことなんでしょうか?

初歩的な質問で恐縮ですが、よろしくご教授ください。

補足日時:2010/10/31 18:10
    • good
    • 0

連投してすみません。


汚染に注意とは書いておいたものの、それだけで大丈夫か不安になったので、より安全なバージョンを書いておきます。
ANo.6のコードは大変危険ですので、決してそのまま設置しないでください。

#!/usr/bin/perl
use strict;

# 汚染に注意
my $fname = $ENV{'QUERY_STRING'};
$fname =~ s/[^0-9a-zA-Z.]/\_/g;
my $url = "http://www.hogehoge.com/" . $fname;

print "Content-type: image/jpeg\n\n";

# サーバーに保存せず直接表示
system(qq/wget -q -O- $url/);

# サーバーに保存しつつ表示
#system(qq/wget -q -O- $url | tee $fname/);
    • good
    • 0
この回答へのお礼

いろいろと教えて頂きありがとうございました。

平均30Kbの画像30個を一つずつ順に取りに行くオリジナルバージョンが、20秒程度かかっていたのが、No4のfork関数やNo5の Parallel::ForkManagerモジュールを使ったバージョンでは劇的に速くなって2秒以下で表示されるようになりました。

このNo6のバージョンはまだ試していませんが、これが速度的には一番ぽいですね。

ありがとうございました。

お礼日時:2010/11/01 10:43

>ブラウザにHTMLソースを送ってしまっても大丈夫てことなんでしょうか?


HTMLの表示後に、JavaScriptで動的に画像を取得するという意味です。
技術的に難しいようなら、無理に実装しなくてもいいと思います。

ところで、仕様的に可能であるなら画像の表示部分をCGIにしてみてはいかがでしょう。
速度的には最速だと思います。

<img src="view.cgi?abc.jpg">

#!/usr/bin/perl
use strict;

# 汚染に注意
my $fname = $ENV{'QUERY_STRING'};
my $url = "http://www.hogehoge.com/" . $fname;

print "Content-type: image/jpeg\n\n";

# サーバーに保存せず直接表示
system("wget -q -O- $url");

# サーバーに保存しつつ表示
#system("wget -q -O- $url | tee $fname");
    • good
    • 0

表示がずれるので、空白2文字を全角空白で書いていることに注意


use strict;
use warnings;
use POSIX 'strftime';

print '### start  : ', strftime("%Y-%m-%d %H:%M:%S", localtime), $/;

my $pid = undef;
for my $count (0 .. 9) {
  $pid = fork;
  if ( !defined $pid ) {
    die "Error Fork : $pid";
  }
  elsif ($pid == 0) {
    # child process
    print "$count sleep ... : ", strftime("%Y-%m-%d %H:%M:%S", localtime), $/;
    sleep 3;
    print "$count wakeup  : ", strftime("%Y-%m-%d %H:%M:%S", localtime), $/;
    exit;
  }
}

# wait all child process
while ((my $wait_pid = wait) != -1) {}
print '### end   : ', strftime("%Y-%m-%d %H:%M:%S", localtime), $/;

--- 結果
$ perl -w wait.pl
### start  : 2010-10-30 23:11:45
2 sleep ... : 2010-10-30 23:11:45
3 sleep ... : 2010-10-30 23:11:45
4 sleep ... : 2010-10-30 23:11:45
5 sleep ... : 2010-10-30 23:11:45
6 sleep ... : 2010-10-30 23:11:45
7 sleep ... : 2010-10-30 23:11:45
8 sleep ... : 2010-10-30 23:11:45
9 sleep ... : 2010-10-30 23:11:45
0 sleep ... : 2010-10-30 23:11:45
1 sleep ... : 2010-10-30 23:11:45
2 wakeup  : 2010-10-30 23:11:48
3 wakeup  : 2010-10-30 23:11:48
4 wakeup  : 2010-10-30 23:11:48
6 wakeup  : 2010-10-30 23:11:48
7 wakeup  : 2010-10-30 23:11:48
8 wakeup  : 2010-10-30 23:11:48
9 wakeup  : 2010-10-30 23:11:48
5 wakeup  : 2010-10-30 23:11:48
0 wakeup  : 2010-10-30 23:11:48
1 wakeup  : 2010-10-30 23:11:48
### end   : 2010-10-30 23:11:48

この回答への補足

実行結果まで書いていただきありがとうございました。

fork()関数てのがあるんですね。
調べてみたのですが、こういう理解でいいのでしょうか。

$pid = fork; という一文だけで、プログラム自体が丸ごと複製され、子プログラムとして起動し(但し、子プロセスの処理の開始は、fork 直後から)、$pidには親プロセスなら子のプロセスID、子なら 0 、コピーに失敗した場合は未定義値が入る。
だから、$pidの中身を調べると自分が親なのかコピーされたクローンなのかをプログラム自身が知ることになる。

> elsif ($pid == 0) {
でもって自分が子ならそれぞれ独立して与えられた仕事をこなし、終了したらexitする。

> while ((my $wait_pid = wait) != -1) {}
親なら全ての子プロセスが終了するまで待って、残りの作業を片付ける。

補足日時:2010/10/31 01:45
    • good
    • 0

No.2で回答した者です。

先ほどは間違えました。

> $x=0;
> while (`ps | awk '/downloader/&&!/awk/{a++};END{print a}'` > 0 || $x < 60) {

これは "while (`.....` > 0 && $x < 60) { ..."とすべきところでした。訂正します。

No.1回答者様がおっしゃっているように、wgetの引数で複数サイトを一度に指定できる場合は、その方が簡便な解決策になるでしょう。同時平行ダウンロードはできませんが、CGIを待たせるコード、タイムラグが不要になります。旧バージョンのwgetではできないみたいなので、場合によりwget、curlなどのアップグレードインストールが必要になるかもしれません。

この回答への補足

ご丁寧な解説ありがとうございました。
そういえば UNIXコマンドで & を付けてバックグラウンドで走らせるというのがありましたね。
すっかり忘れてしまっています(^^;;

$x=0;
while (`ps | awk '/downloader/&&!/awk/{a++};END{print a}'` > 0 && $x < 60) {
sleep 1;
$x++;
}

これは、バックグラウンドで起動させた複数の「downloader」という名前のプログラムのプロセス数を awk で数えて変数「a」に加えていき、最後にEND{print a}で出力させて、それが0個になるまで1秒ごとに最大60回まで調べに行くということでいいでしょうか?

補足日時:2010/10/30 23:20
    • good
    • 0

こんにちは。

おっしゃりたいことを正確に理解しているかどうか自信がないのですが、

> 結局親プログラムは呼び出した一つの子プログラムの終了まで待って次に行くみたいなので意味ないかと・・・。

Linux (Kernel 2.6.21.7) + Perl 5.8.8で以下のように実行すると、xtermが3つ連続起動した後にperlが終了します。バックグラウンドで起動しますので、xtermの終了を待たずにperlは終了します。

perl -e 'for(1..3){system("xterm &")}'

ですので、system()でダウンロードプロセスを起動する際に&を後ろに付けてバックグラウンドに回せば、CGIはそのまま次の動作に移るのではないかと思います。ただし、複数起動させたダウンロードプロセスの終了をCGIが待たなければならないと思いますので、ダウンロードプロセス起動後に

$x=0;
while (`ps | awk '/downloader/&&!/awk/{a++};END{print a}'` > 0 || $x < 60) {
sleep 1;
$x++;
}

などとして、CGIを一時停止する必要があるかもしれません。上記コードでは最大60秒間停止することになります。
    • good
    • 0

可能ですが、すべての取得が終わったことを待ち合わせないといけないので、すこし難しくなります。

「こういうことが可能か?」と質問するレベルのスキルでは難しいと思います。

wgetの引数に複数のURLを指定出来るので、複数指定すると1ファイルずつwgetするよりはずいぶん速くなります。平行して複数を取得する訳じゃないですが、wgetの起動が一度で済むので。
    • good
    • 0
この回答へのお礼

> すべての取得が終わったことを待ち合わせないといけないので、すこし難しくなります。

確かに初心者には難しそうですね。
でも可能だとわかったので、勉強してみようと思います。
ありがとうございました。

お礼日時:2010/10/30 22:24

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!