I have been build some background apps recently which need to do a lot of repetitions (think 100,000 times the same task which takes about 0.25 to 1 second to execute, of which most of it is waiting for the remote server to respond). Since these are daily and are not that server intensive it made sense to run them concurrently so instead of taking the good part of the day it just takes a few hours. I noticed on the proc_open page there was a very nice solution by Matou Havlena of havlena.net.

I have modified the solution a little to make it easier to read and made the output optional.

If you want to fork the solution it is on GitHub:
https://github.com/dalehurley/PHP-Process-Manager

Alternatively you can download the code:
https://dalehurley.com/playground/process_manager.zip

If you want to see a basic example:
https://dalehurley.com/playground/example.php (warning this takes a little while to load)

class Processmanager {
	public $executable       = "php";	//the system command to call
	public $root             = "";		//the root path
	public $processes        = 3;		//max concurrent processes
	public $sleep_time       = 2;		//time between processes
	public $show_output      = false;	//where to show the output or not

	private $running          = array();//the list of scripts currently running
	private $scripts          = array();//the list of scripts - populated by addScript
	private $processesRunning = 0;		//count of processes running

	function addScript($script, $max_execution_time = 300)
	{
		$this->scripts[] = array("script_name" => $script, "max_execution_time" => $max_execution_time);
	}

	function exec()
	{
		$i = 0;
		for(;;)
		{
			// Fill up the slots
			while (($this->processesRunning<$this->processes) and ($i<count($this->scripts)))
			{
				if($this->show_output)
				{
					ob_start();
					echo "<span style='color: orange;'>Adding script: ".$this->scripts[$i]["script_name"]."</span><br />";
					ob_flush();
					flush();
				}
				$this->running[] =& new Process($this->executable, $this->root, $this->scripts[$i]["script_name"], $this->scripts[$i]["max_execution_time"]);
				$this->processesRunning++;
				$i++;
			}

			// Check if done
			if (($this->processesRunning==0) and ($i>=count($this->scripts))) {
				break;
			}

			// sleep, this duration depends on your script execution time, the longer execution time, the longer sleep time
			sleep($this->sleep_time);

			// check what is done
			foreach ($this->running as $key => $val)
			{

				if (!$val->isRunning() or $val->isOverExecuted())
				{
					if($this->show_output)
					{
						ob_start();
						if (!$val->isRunning())
						{
							echo "<span style='color: green;'>Done: ".$val->script."</span><br />";
						}
						else
						{
							echo "<span style='color: red;'>Killed: ".$val->script."</span><br />";
						}
						ob_flush();
						flush();
					}
					proc_close($val->resource);
					unset($this->running[$key]);
					$this->processesRunning--;
				}
			}
		}
	}
}

class Process {
	public $resource;
	public $pipes;
	public $script;
	public $max_execution_time;
	public $start_time;

	function __construct(&$executable, &$root, $script, $max_execution_time)
	{
		$this->script = $script;
		$this->max_execution_time = $max_execution_time;
		$descriptorspec    = array(
			0 => array('pipe', 'r'),
			1 => array('pipe', 'w'),
			2 => array('pipe', 'w')
		);
		$this->resource    = proc_open($executable." ".$root.$this->script, $descriptorspec, $this->pipes, null, $_ENV);
		$this->start_time = mktime();
	}

	// is still running?
	function isRunning()
	{
		$status = proc_get_status($this->resource);
		return $status["running"];
	}

	// long execution time, proccess is going to be killer
	function isOverExecuted()
	{
		if ($this->start_time+$this->max_execution_time<mktime()) return true;
		else return false;
	}
}

Quick Start – Load the process manager and some scripts:

include_once('class.process_manager.php');		//load the class file
$manager              = new Processmanager();	//create the manager object
$manager->executable  = "php";					//the Linux executable
$manager->path        = "";						//path to the scripts to run
$manager->show_output = true;					//show the output of the manager
$manager->processes   = 3;						//max concurrent processes
$manager->sleep_time  = 1;						//time between checking if the processes are complete
$manager->addScript("sleep.php", 2);			//add a script and it max execution time in seconds
$manager->addScript("sleep.php", 2);
$manager->addScript("sleep.php", 1);
$manager->addScript("sleep.php", 4);
$manager->addScript("sleep.php", 5);
$manager->addScript("sleep.php");				//no max execution time defaults to 300 seconds
$manager->exec();								//start processing through the code
echo 'Completed all tasks';

Acknowledgements
Matou Havlena of havlena.net came up with the original concept and posted it on PHP.NET

Advertisements

One thought on “PHP Multi-thread Asynchronous Process Manager

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s