私は週末に 2 つの企業と面接するために北京に行き、面接中に何人かの技術専門家と会い、たくさんのことを学びました。私は自分の欠点に気づき、コンピューター学習に対する自信を強めました。この記事は、面接での質問の 1 つをまとめたものです。
インタビューでうまく答えられなかった質問がありました。タイトルは次のとおりです。 3 つの http リクエストを同時に実行し、そのうちの 1 つのリクエストが結果を返すと、他の 2 つのリクエストは中断されます。
その時考えていた内容は、本来の質問の意図とは少しずれていましたが、おそらく client->recv() より前に結果が生成されているかどうかを判断するために http リクエストを中断する方法を考えていました。その答えは、ソケットを使用して http リクエストを送信することでした。ソケットは libevent ループ監視に参加し、結果が取得されたかどうかを判定し、結果が取得された場合は直接返します。
話せば話すほど違和感を感じましたが、recv の結果が得られているので http リクエストを中断したとは言えません。しかも、私はlibeventを使ったことはありません。後で、2 つの実装について説明しました。1 つは、curl_multi_init を使用すること、もう 1 つは、golang を使用して同時実行性を実現することです。
当時のgolang版ではcloseの使い方を忘れており、質問の意味を完全に満たす結果にはなりませんでした。
私はこの質問に答えることができませんでしたが、試験官は私に何も問題を与えませんでした。しかし、面接が終わって階下に降りるまでそれについて考え続け、おそらく試験は同時実行性とプロセス スレッドの適用に関するものだったのかもしれません。この記事を要約するために、PHP の同時実行性について話しましょう。この記事では、PHP プログラミングにおける 5 つの同時実行メソッドを大まかに要約します。最終的な Golang 実装はまったく退屈なので無視してかまいません。時間があれば、別の libevent バージョンが追加されます。
ドキュメントには、複数の cURL ハンドルの非同期処理を許可すると書かれていますが、これは確かに非同期です。ここで理解する必要があるのは select メソッドです。これについては、curl_multi 接続のいずれかでアクティビティが発生するまでブロックするというドキュメントで説明されています。一般的な非同期モデルを理解すれば理解できると思いますが、非常に優れた記事を引用していますので、興味のある方は読んでみてください。
<?php// build the individual requests as above, but do not execute them$ch_1 = curl_init('http://www.baidu.com/');$ch_2 = curl_init('http://www.baidu.com/');curl_setopt($ch_1, CURLOPT_RETURNTRANSFER, true);curl_setopt($ch_2, CURLOPT_RETURNTRANSFER, true);// build the multi-curl handle, adding both $ch$mh = curl_multi_init();curl_multi_add_handle($mh, $ch_1);curl_multi_add_handle($mh, $ch_2);// execute all queries simultaneously, and continue when all are complete$running = null;do { curl_multi_exec($mh, $running); $ch = curl_multi_select($mh); if($ch !== 0){ $info = curl_multi_info_read($mh); if($info){ var_dump($info); $response_1 = curl_multi_getcontent($info['handle']); echo "$response_1 \n"; break; } }} while ($running > 0);//close the handlescurl_multi_remove_handle($mh, $ch_1);curl_multi_remove_handle($mh, $ch_2);curl_multi_close($mh);
ここで設定したのは、select が結果を取得したときにループを終了し、curl リソースを削除して http リクエストをキャンセルすることです。
swoole_client は非同期モードを提供します。実はこれを忘れていました。ここでのスリープ方法では、swoole バージョンが 1.7.21 以上である必要があります。私はまだこのバージョンにアップグレードしていないので、直接終了できます。
<?php$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);//设置事件回调函数$client->on("connect", function($cli) { $req = "GET / HTTP/1.1\r\n Host: www.baidu.com\r\n Connection: keep-alive\r\n Cache-Control: no-cache\r\n Pragma: no-cache\r\n\r\n"; for ($i=0; $i < 3; $i++) { $cli->send($req); }});$client->on("receive", function($cli, $data){ echo "Received: ".$data."\n"; exit(0); $cli->sleep(); // swoole >= 1.7.21});$client->on("error", function($cli){ echo "Connect failed\n";});$client->on("close", function($cli){ echo "Connection close\n";});//发起网络连接$client->connect('183.207.95.145', 80, 1);
ねえ、swoole_process のことを忘れてたので、ここには pcntl モジュールは必要ありません。しかし、書いてみると、これは実際には割り込み要求ではなく、最初に来たものが読み取られ、その後の戻り値は無視されることがわかりました。
<?php$workers = [];$worker_num = 3;//创建的进程数$finished = false;$lock = new swoole_lock(SWOOLE_MUTEX);for($i=0;$i<$worker_num ; $i++){ $process = new swoole_process('process'); //$process->useQueue(); $pid = $process->start(); $workers[$pid] = $process;}foreach($workers as $pid => $process){ //子进程也会包含此事件 swoole_event_add($process->pipe, function ($pipe) use($process, $lock, &$finished) { $lock->lock(); if(!$finished){ $finished = true; $data = $process->read(); echo "RECV: " . $data.PHP_EOL; } $lock->unlock(); });}function process(swoole_process $process){ $response = 'http response'; $process->write($response); echo $process->pid,"\t",$process->callback .PHP_EOL;}for($i = 0; $i < $worker_num; $i++) { $ret = swoole_process::wait(); $pid = $ret['pid']; echo "Worker Exit, PID=".$pid.PHP_EOL;}
pthreads モジュールをコンパイルすると、PHP をコンパイルするときに ZTS をオンにする必要があるというプロンプトが表示されるため、wamp の多くの PHP はたまたま TS であるため、スレッド セーフ バージョンを使用する必要があるようです。 dll を作成し、文書内の指示を対応するディレクトリにコピーし、win でテストしました。 まだよく理解できていないのですが、PHPのpthreadとPOSIXのpthreadは全く違うという記事を見つけました。コードは少し悪いので、感触を得るにはもっとドキュメントを読む必要があります。
<?phpclass Foo extends Stackable { public $url; public $response = null; public function __construct(){ $this->url = 'http://www.baidu.com'; } public function run(){}}class Process extends Worker { private $text = ""; public function __construct($text,$object){ $this->text = $text; $this->object = $object; } public function run(){ while (is_null($this->object->response)){ print " Thread {$this->text} is running\n"; $this->object->response = 'http response'; sleep(1); } }}$foo = new Foo();$a = new Process("A",$foo);$a->start();$b = new Process("B",$foo);$b->start();echo $foo->response;
yield によって生成されたジェネレーターは、関数を中断し、send を使用してジェネレーターにメッセージを送信できます。コルーチンのバージョンは今後追加される予定です。まだ勉強中です。
Go での実装は比較的簡単です。家に帰ってから閉じて確認し、パニックに対処するだけで大丈夫です。コードは次のとおりです:
package mainimport ( "fmt")func main() { var result chan string = make(chan string, 1) for index := 0; index< 3; index++ { go doRequest(result) } res, ok := <-result if ok { fmt.Println("received ", res) }}func doRequest(result chan string) { response := "http response" defer func() { if x := recover(); x != nil { fmt.Println("Unable to send: %v", x) } }() result <- response close(result)}
上記のメソッドについては、質問に適合すると思われるcurl_multi_*を除いて(よくわかりません。ソースコードを確認してください)、他のメソッドはリクエスト後のrecv()操作を中断しません。レスポンスが得られた後にフォローアップがあり、オペレーションがあれば便利ですが、そうでなければ意味がありません。考えてみれば、PHP の操作の粒度が大きすぎるのかもしれません。C/C++ を使用すれば問題は解決できるはずです。
書いたときは問題に気づきませんでしたが、メソッドによっては戻り値を直接出力する場合があります。これは、リクエストの結果を取得するために一律に使用する必要があります。私の能力には限界があるので、とりあえずこれでやっておこう。
最後に宣伝したいと思います。 Jisuanke はコンピューター サイエンスの高度な教育に特化した会社です。プログラミングやコンピューターの基礎に興味がある場合は、その Web サイトにアクセスして学ぶとよいでしょう。
同時に、会社は常に人材を募集していますので、自分の能力に自信がある場合は、試してみることができます。同社は非常に自由でオープンで、大半が 1990 年代生まれです。 ACM世界チャンピオンやZhihuの専門家など、素晴らしい人もたくさんいます。
この会社は教育を専門としているので、社内の学習教材も充実しているはずです。OSに関する試験問題をいくつか見ただけですが、幅広い知識が含まれており、会社の平均的な技術力がいかに強力であるかを示しています。
記事に抜け漏れや間違いがあった場合は、遠慮なくご指摘いただき、新人の向上に貢献していただければ幸いです。
原文 http://segmentfault.com/a/1190000004069411