上記の一連のチュートリアル:
PHP マルチプロセス プログラミング (1)
PHP マルチプロセス プログラミング (2) パイプライン通信
PHP マルチプロセスプログラミング (3) マルチプロセス Web クローリングのデモ
次に、unix サーバーのみに対応したマルチプロセスについて説明します。 window と unix の両方 (ここでは一般的に指します。以下のカールは実際には IO 多重化によって実装されています)。
拡張機能を使用してマルチスレッドを実装する典型的な例は、CURL がマルチスレッド Web ページ クローリングをサポートしていることです。
この部分は抽象的すぎるため、最初に複数の Web ページ コンテンツを並行してクロールするための CURL のパッケージ化クラスを提供します。このクラスは実際には非常に実用的です。
これらの関数の内部実装の詳細な分析については、次のチュートリアルで説明します。
このクラスはよく理解できないかもしれませんが、PHP CURL の公式ホームページに多くのエラー例が掲載されているので、その内部機構
を説明すると理解できるようになります。
最初にコードを見てください:
class Http_MultiRequest{ //要并行抓取的url 列表 private $urls = array(); //curl 的选项 private $options; //构造函数 function __construct($options = array()) { $this->setOptions($options); } //设置url 列表 function setUrls($urls) { $this->urls = $urls; return $this; } //设置选项 function setOptions($options) { $options[CURLOPT_RETURNTRANSFER] = 1; if (isset($options['HTTP_POST'])) { curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $options['HTTP_POST']); unset($options['HTTP_POST']); } if (!isset($options[CURLOPT_USERAGENT])) { $options[CURLOPT_USERAGENT] = 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1;)'; } if (!isset($options[CURLOPT_FOLLOWLOCATION])) { $options[CURLOPT_FOLLOWLOCATION] = 1; } if (!isset($options[CURLOPT_HEADER])) { $options[CURLOPT_HEADER] = 0; } $this->options = $options; } //并行抓取所有的内容 function exec() { if(empty($this->urls) || !is_array($this->urls)) { return false; } $curl = $data = array(); $mh = curl_multi_init(); foreach($this->urls as $k => $v) { $curl[$k] = $this->addHandle($mh, $v); } $this->execMulitHandle($mh); foreach($this->urls as $k => $v) { $data[$k] = curl_multi_getcontent($curl[$k]); curl_multi_remove_handle($mh, $curl[$k]); } curl_multi_close($mh); return $data; } //只抓取一个网页的内容。 function execOne($url) { if (empty($url)) { return false; } $ch = curl_init($url); $this->setOneOption($ch); $content = curl_exec($ch); curl_close($ch); return $content; } //内部函数,设置某个handle 的选项 private function setOneOption($ch) { curl_setopt_array($ch, $this->options); } //添加一个新的并行抓取 handle private function addHandle($mh, $url) { $ch = curl_init($url); $this->setOneOption($ch); curl_multi_add_handle($mh, $ch); return $ch; } //并行执行(这样的写法是一个常见的错误,我这里还是采用这样的写法,这个写法 //下载一个小文件都可能导致cup占用100%, 并且,这个循环会运行10万次以上 //这是一个典型的不懂原理产生的错误。这个错误在PHP官方的文档上都相当的常见。) private function execMulitHandle2($mh) { $i = 0; $running = null; do { curl_multi_exec($mh, $running); $i++; } while ($running > 0); //var_dump($i); } //应该用这样的写法 private function execMulitHandle($mh) { $i = 0; do {$mrc = curl_multi_exec($mh,$active); $i++;} while ($mrc == CURLM_CALL_MULTI_PERFORM); while ($active && $mrc == CURLM_OK) { if (curl_multi_select($mh) != -1) { do {$mrc = curl_multi_exec($mh, $active); $i++;} while ($mrc == CURLM_CALL_MULTI_PERFORM); } $i++; } //var_dump($i); }}
最も多くのコメントを持つ最後の関数を見てください。プログラムは完全に正常であるため、通常のデバッグではこのエラーを見つけるのは簡単ではないかもしれません。ただし、運用サーバーでは、すぐにクラッシュの影響が発生します。
これができない理由の説明は、C 言語の内部実装の観点から分析する必要があります。この部分は次のチュートリアル (PHP 高度なプログラミング – シングルスレッドの並列 Web フェッチ) に移動されます。ただし、この原理は C 言語ではなく、PHP の
クラスで表現されています。実際、これまでに費やした 4 つのチュートリアルの実装は非常に簡単ですが、多大な労力がかかりました。 . マルチスレッドWebページクローリング機能を実装しました。純粋な PHP の実装では、より良い実装を実現するにはバックグラウンド サービスを使用するしかありませんが、オペレーティング システムのインターフェイス言語である C 言語を使用すると、当然のことながら、この実装はよりシンプルで、より柔軟で、より効率的になります。
同時に複数の Web ページをクロールするだけで簡単ですが、実際には下位レベルで多くの処理が必要になります。途中で僧侶になってしまった多くの PHP プログラマーにとって、複数の Web ページについて話したくないかもしれません。オペレーティング システムに関して言えば、簡単に言うと、複数の「プログラム」を並行して実行することを意味します。ただし、多くの場合、マルチスレッドが不可欠です。たとえば、より高速なクローラーを作成したい場合、労力が無駄になることがよくあります。ただし、PHP プログラマは、この拡張機能の CURL に感謝する必要があります。これにより、中規模のクローラを作成するためにあまり熟練していない Python を使用する必要がなくなりました。この内部マルチスレッドがあれば十分です。
最後に、上記のクラスのテスト例:
$urls = array("http://baidu.com", "http://baidu.com", "http://baidu.com", "http://baidu.com", "http://baidu.com", "http://baidu.com", "http://www.google.com", "http://www.sina.com.cn", );$m = new Http_MultiRequest();$t = microtime(true);$m->setUrls($urls);//parallel fetch(并行抓取):$data = $m->exec();$parallel_time = microtime(true) - $t;echo $parallel_time . "\n";$t = microtime(true);//serial fetch(串行抓取):foreach ($urls as $url){ $data[] = $m->execOne($url);}$serial_time = microtime(true) - $t;echo $serial_time . "\n";