Наткнулся на пост на хабре, название которого меня давно интересовало. Решение, описанное там, не относилось конечно к желаемому, но всё же имеет место быть. Правда мне класс показался не совсем удобным, например нельзя указать функцию для обработки ответа, нет отчета по времени выполнения, не совсем удобно передаются данные да и вообще в коде ошибки есть. Немного переделал решение и выкладываю тут: PHP: <?php class MultiThreading { /** * функция для обработки ответа * * @var string * @access private */ private $callback; /** * Имя сервера * * @var string * @access private */ private $server; /** * Максимальное количество потоков * * @var int * @access private */ private $maxthreads; /** * Имя скрипта, который выполняет нужную нам задачу * * @var string * @access private */ private $scriptname; /** * Параметры, которые мы будем передавать скрипту * * @var array * @access private */ private $params = array(); /** * Массив, в котором хранятся потоки * * @var array * @access private */ private $threads = array(); /** * Массив, содержащий время выполнения работы * * @var array * @access private */ private $times = array(0, 0); /** * Массив, в котором хранятся результаты * * @var array * @access private */ private $results = array(); /** * Конструктор класса. В нем мы указываем максимальное количество потоков и имя сервера. Оба аргумента необязательны. * * @param int $maxthreads максимальное количество потоков, по умолчанию 10 * @param string $callback имя функции для обработки ответа * @param string $server имя сервера, по умолчанию имя сервера, на котором запущено приложение * @access public */ public function __construct($maxthreads = 10, $callback = '', $server = '') { $this->server = ($server)? $server : $_SERVER['SERVER_NAME']; $this->maxthreads = $maxthreads; if ($callback) $this->callback = $callback; } /** * Указываем имя скрипта, который выполняет нужную нам задачу * * @param string $scriptname имя скрипта, включая путь к нему * @access public */ public function setScriptName($scriptname) { if (!$fp = fopen('http://'.$this->server.'/'.$scriptname, 'r')) throw new Exception('Cant open script file'); fclose($fp); $this->scriptname = $scriptname; } /** * Возвращаем время, затраченное на выполнение всей работы * * @param null * @access public */ public function getTime() { return round($this->times[1] - $this->times[0]); } /** * Задаем параметры, которые мы будем передавать скрипту * * @param array $params массив параметров * @access public */ public function setParams($params = array()) { $this->params = $params; } /** * Выполняем задачу, комментарии в коде * * @access public */ public function execute() { $this->times[0] = microtime(true); // Запускаем механизм, и он работает, пока не выполнятся все потоки do { // Если не превысили лимит потоков if (sizeof($this->threads) < $this->maxthreads) { // Если удается получить следующий набор параметров if ($item = current($this->params)) { // Формируем запрос методом GET $query = "GET [url=http://]http://[/url]".$this->server."/".$this->scriptname."?".$item." HTTP/1.0\r\n"; // Открыватем соединение if (!$fsock = fsockopen($this->server, 80)) throw new Exception('Cant open socket connection'); fputs($fsock, $query); fputs($fsock, "Host: {$this->server}\r\n"); fputs($fsock, "\r\n"); stream_set_blocking($fsock, 0); stream_set_timeout($fsock, 3600); // Записываем поток $this->threads[] = $fsock; $this->results[] = ''; // Переходим к следующему элементу next($this->params); } } // Перебираем потоки foreach ($this->threads as $key=>$value) { // Если поток отработал, закрываем и удаляем, вызываем пользовательскую функцию if (feof($value)) { fclose($value); unset($this->threads[$key]); if ($this->callback) call_user_func($this->callback, $this->results[$key]); } else { // Иначе считываем результаты $this->results[$key] .= fgets($value,4096); } } usleep(500); // ... пока не выполнятся все потоки } while (sizeof($this->threads) > 0); $this->times[1] = microtime(true); return $this->results; } } ?> Всегда с удовольствием выслушаю Ваши замечания.
Ах да, пример использования: PHP: <? function r_callback($in) { echo array_pop(explode(PHP_EOL.PHP_EOL,$in)); } $multithreading = new MultiThreading(20,'r_callback'); try { $multithreading->setScriptName('groups/task2secure.php'); $multithreading->setParams($data); $multithreading->execute(); } catch (Exception $e) { print_r($e); } echo 'Время выполнения:' . $multithreading->getTime() . ' секунд'; ?>
Замечание только одно: РНР не создан для многопоточности. Поэтому она там нафиг сдалась, если нужно писать бота 24/7, то лучше для этого выбрать более подходящие инструменты. Автор ссылается на то, что ему нужна многопоточность для рассылки писем. Только вот нужно учитывать, что за такой спам любой хостер заблокирует.
Все прекрасно знают про ограничения в этом плане и каждый вы*бывается по своему, чтобы нужную задачу решить. Замечания нужны именно по коду.
Ensiferum Потестировал на скрипте counter: PHP: <?php for ($i = $_GET['start']; $i < $_GET['limit']; $i++); echo $i; PHP: <?php $th = new MultiThreading(10, 'cleanHeaders', '192.168.1.11'); $th->setScriptName('multi/counter.php'); $param = 'start=1&limit=10000000'; $params = array(); for ($i=0; $i<2; $i++) { $params[] = $param; } $th->setParams($params); echo array_sum($th->execute()); Так вот, мультипочное выполнение всегда было в 2 раза дольше однопоточного - хоть до 20 млн. двумя потоками (5 с. против 2.5), хоть до 100 млн. 10 потоков (25 с. против 12). Кстати, задержка практически оптимальная - её увеличение на данном тесте не сказалось, а без нее over 30 секунд, хотя если потоков много, то чем реже они будут впустую перебираться, тем лучше. Все так интересует задача, с которой многопоточный режим справится быстрее.
Очень просто. Эта задача — скачивание какойнить хрени с чужого сайта (разумеется, с целью копипасты на свой сайт). Правда до сих пор не понимаю на кой тогда люди wget придумали.
Я знаю про мультикурл и для простых запросов использую rolling curl. Но у этой системы один затык: только когда ВСЯ порция отработает, стартует новая порция, а так, дааа, в несколько потоков. А мне нужно несколько действий курлом совершить (сложная авторизация, кукисы, хеши, пост / гет). Раньше эмитировал многопоточность через AJAX так же ссылаясь на обработчик. Но решение выше при том же количестве потоком работает раз в 10 быстрее.