Многопоточность в PHP

Существует ли в PHP многопоточность?

PHP относится к языкам, в которых поддержка многопоточности отсутствует. Но, есть немало задач, в которых она была бы очень полезна. В большинстве случаев вам не нужно порождать и создавать новых потоков вообще и можно получить отличную производительность и без этого.

Сокеты

Очень полезная штука, когда вам надо дёргать чужие страницы или просто посылать запросы к другим сайтам.

Недавно поступил заказ на скрипт, который будет дёргать информацию с одного сайта, сохранять в базу, а потом переносить на другой. Порой скрипт делает запросы к 100+ страницам. Если этот скрипт бы выполнялся последовательно, то заняло бы много времени.

В этом деле поможет функция stream_socket_client

Прелесть в том, что, создавая запросы к сайтам этой функцией в асинхронном режиме, нам не придётся ждать ответа. Задача состоит из двух частей.

Создать сокеты
$flag = STREAM_CLIENT_ASYNC_CONNECT|STREAM_CLIENT_CONNECT;
$sockets=array();
$id=0;
$timeout = 60;
$convenient_read_block=8192;
$result=array(); 
$url = array();
for($i=$this->beg_ID;$i<=$this->end_ID;++$i)
{

	$fp = stream_socket_client('tcp://'.$host.':'.$this->port, $err, $errstr, 8, $flag);
	if (!$fp) 
	{
		echo 'httpPost error: '.$errstr;
		return NULL;
	}
	else
	{
		stream_set_blocking($fp, 0); 
		$sockets[$id++] = $fp;
		$url[$id-1] = $i;
	}
}

Как видно, создаются сокеты и ссылки на них сохраняются в массиве $sockets. Теперь необходимо организовать прослушку ответов.

Ждём ответа

В некоторых источниках видел исходники, где функция на запись в сокет вызывается сразу после создания(применяли к $fp). На самом деле необходимо организовать прослушку на готовность сокета и к чтению, и к записи.

//создаём массивы сокетов для прослушки
$toRead = $toWrite = $sockets; 
//пока есть сокеты, чтение с которых мы ждём
while (count($toRead) > 0) 
{			
	$read=$toRead;
	$write=$toWrite; 
	
	stream_select($read, $write, $e=null, $timeout); 
	//есть сокеты, готовые к записи
	if (count($write))
	{
		foreach ($write as $w)
		{
			
			$id=array_search($w, $sockets); 
			//$query="GET /product.php?pid=PD".$id." HTTP/1.0\r\n".
			$query="GET /product.php?pid=PD0".$url[$id]." HTTP/1.0\r\n".
			"Host: ".$host."\r\n".
			"User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1\r\n".
			"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*\/*;q=0.8".
			"Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3\r\n".
			"Accept-Encoding: gzip, deflate\r\n".
			"Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7\r\n".
			"Connection: close\r\n\r\n";
			fwrite($w, $query);
			unset($toWrite[array_search($w, $toWrite)]);
		}
	}
	//есть сокеты, готовые к чтению
	if (count($read)) 
	{
		
		foreach ($read as $r) 
		{ 
			$id=array_search($r, $sockets); 
			if(!isset($result[$id]))
			$result[$id]='';
			$data=fread($r, $convenient_read_block); 

			if (strlen($data) == 0) 
			{ 
				fclose($r); 
				unset($toRead[array_search($r, $toRead)]);
			} 
			else 
			{ 
				$result[$id] .= $data; 
			} 
		} 
	}
	
}

Мы используем функцию stream_select() для ожидания возникновения событий на открытых сокетах. Вы можете ожидать готовности чтения, записи или исключительных событий (параметр первый, второй и третий соответственно). stream_select() будет ждать $timeout секунд пока событие не появится – когда же это случится, функция будет модифицировать массивы, которые Вы ей передали, так что они будут содержать идентификаторы сокетов, удовлетворяющих Вашему критерию.

Ну, как примечание, скажу, что доступна эта прелесть с PHP 5.