curl_multi_select($mh, $timeout) simply blocks for $timeout seconds while curl_multi_exec() returns CURLM_CALL_MULTI_PERFORM. Otherwise, it works as intended, and blocks until at least one connection has completed or $timeout seconds, whatever happens first.
For that reason, curl_multi_exec() should always be wrapped:
<?php
function full_curl_multi_exec($mh, &$still_running) {
do {
$rv = curl_multi_exec($mh, $still_running);
} while ($rv == CURLM_CALL_MULTI_PERFORM);
return $rv;
}
?>
With that, the core of "multi" processing becomes (ignoring error handling for brevity):
<?php
full_curl_multi_exec($mh, $still_running); do { curl_multi_select($mh); full_curl_multi_exec($mh, $still_running); while ($info = curl_multi_info_read($mh)) {
}
} while ($still_running);
?>
Note that after starting requests, retrieval is done in the background - one of the better shots at parallel processing in PHP.