php-thread/Thread.php

258 lines
5.6 KiB
PHP

<?php
/**
* Implements threading in PHP
*
* PHP version 5
*
* @category Threading
* @package None
* @author Tudor Barbu <miau@motane.lu>
* @copyright 2009 MIT
* @license http://en.wikipedia.org/wiki/MIT_License MIT License
* @link http://blog.motane.lu/2009/01/02/multithreading-in-php/
*/
/**
* Thread class
*
* @category Threading
* @package None
* @author Tudor Barbu <miau@motane.lu>
* @copyright 2009 MIT
* @license http://en.wikipedia.org/wiki/MIT_License MIT License
* @link http://blog.motane.lu/2009/01/02/multithreading-in-php/
*/
class Thread
{
const FUNCTION_NOT_CALLABLE = 10;
const COULD_NOT_FORK = 15;
/**
* Possible errors
*
* @var array
*/
private $_errors = array(
Thread::FUNCTION_NOT_CALLABLE => 'You must specify a valid function name that can be called from the current scope.',
Thread::COULD_NOT_FORK => 'pcntl_fork() returned a status of -1. No new process was created'
);
/**
* Callback for the function that should run as a separate thread
*
* @var callback
*/
protected $runnable;
/**
* Holds the current process id
*
* @var integer
*/
private $_pid;
/**
* Exits with error
*
* @return void
*/
private function fatalError($errorCode){
throw new Exception( $this->getError($errorCode) );
}
/**
* Checks if threading is supported by the current PHP configuration
*
* @return boolean
*/
public static function isAvailable()
{
$required_functions = array(
'pcntl_fork',
);
foreach ( $required_functions as $function ) {
if ( !function_exists($function) ) {
return false;
}
}
return true;
}
/**
* Class constructor - you can pass
* the callback function as an argument
*
* @param callback $runnable Callback reference
*/
public function __construct( $runnable = null )
{
if(!Thread::isAvailable() )throw new Exception("Threads not supported");
if ( $runnable !== null ) {
$this->setRunnable($runnable);
}
}
/**
* Sets the callback
*
* @param callback $runnable Callback reference
*
* @return callback
*/
public function setRunnable( $runnable )
{
if ( self::isRunnableOk($runnable) ) {
$this->runnable = $runnable;
} else {
$this->fatalError(Thread::FUNCTION_NOT_CALLABLE);
}
}
/**
* Gets the callback
*
* @return callback
*/
public function getRunnable()
{
return $this->runnable;
}
/**
* Checks if the callback is ok (the function/method
* is runnable from the current context)
*
* can be called statically
*
* @param callback $runnable Callback
*
* @return boolean
*/
public static function isRunnableOk( $runnable )
{
return ( is_callable($runnable) );
}
/**
* Returns the process id (pid) of the simulated thread
*
* @return int
*/
public function getPid()
{
return $this->_pid;
}
/**
* Checks if the child thread is alive
*
* @return boolean
*/
public function isAlive()
{
$pid = pcntl_waitpid($this->_pid, $status, WNOHANG);
return ( $pid === 0 );
}
/**
* Starts the thread, all the parameters are
* passed to the callback function
*
* @return void
*/
public function start()
{
$pid = @pcntl_fork();
if ( $pid == -1 ) {
$this->fatalError(Thread::COULD_NOT_FORK);
}
if ( $pid ) {
// parent
$this->_pid = $pid;
} else {
// child
pcntl_signal(SIGTERM, array( $this, 'handleSignal' ));
$arguments = func_get_args();
if ( !empty($arguments) ) {
call_user_func_array($this->runnable, $arguments);
} else {
call_user_func($this->runnable);
}
exit( 0 );
}
}
/**
* Attempts to stop the thread
* returns true on success and false otherwise
*
* @param integer $signal SIGKILL or SIGTERM
* @param boolean $wait Wait until child has exited
*
* @return void
*/
public function stop( $signal = SIGKILL, $wait = false )
{
if ( $this->isAlive() ) {
posix_kill($this->_pid, $signal);
if ( $wait ) {
pcntl_waitpid($this->_pid, $status = 0);
}
}
}
/**
* Alias of stop();
*
* @param integer $signal SIGKILL or SIGTERM
* @param boolean $wait Wait until child has exited
*
* @return void
*/
public function kill( $signal = SIGKILL, $wait = false )
{
return $this->stop($signal, $wait);
}
/**
* Gets the error's message based on its id
*
* @param integer $code The error code
*
* @return string
*/
public function getError( $code )
{
if ( isset( $this->_errors[$code] ) ) {
return $this->_errors[$code];
} else {
return "No such error code $code ! Quit inventing errors!!!";
}
}
/**
* Signal handler
*
* @param integer $signal Signal
*
* @return void
*/
protected function handleSignal( $signal )
{
switch( $signal ) {
case SIGTERM:
exit( 0 );
break;
}
}
}