Skip to content

Instantly share code, notes, and snippets.

@ah01
Created March 16, 2012 00:18

Revisions

  1. ah01 revised this gist Mar 19, 2012. 2 changed files with 26 additions and 2 deletions.
    22 changes: 22 additions & 0 deletions example.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,22 @@
    <?php

    require "thread.php";

    function doSomething($res, $t) {
    usleep($t);
    exit($res);
    }

    $thread1 = new Thread('doSomething');
    $thread2 = new Thread('doSomething');
    $thread3 = new Thread('doSomething');

    $thread1->start(3, 10);
    $thread2->start(2, 40);
    $thread3->start(1, 30);

    while ($thread1->isAlive(1) || $thread2->isAlive(2) || $thread3->isAlive(3));

    echo "Thread 1 exit code (should be 3): " . $thread1->getExitCode() . "\n";
    echo "Thread 2 exit code (should be 2): " . $thread2->getExitCode() . "\n";
    echo "Thread 3 exit code (should be 1): " . $thread3->getExitCode() . "\n";
    6 changes: 4 additions & 2 deletions thread.php
    Original file line number Diff line number Diff line change
    @@ -127,22 +127,24 @@ public function getPid() {
    */
    public function isAlive() {
    $pid = pcntl_waitpid( $this->pid, $status, WNOHANG );

    if ($pid === 0) { // child is still alive
    return true;
    } else {
    if (pcntl_wifexited($starts)) { // normal exit
    if (pcntl_wifexited($status) && $this->exitCode == -1) { // normal exit
    $this->exitCode = pcntl_wexitstatus($status);
    }
    return false;
    }
    }

    /**
    * return exit code of child
    * return exit code of child (-1 if child is still alive)
    *
    * @return int
    */
    public function getExitCode() {
    $this->isAlive();
    return $this->exitCode;
    }

  2. ah01 created this gist Mar 16, 2012.
    234 changes: 234 additions & 0 deletions thread.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,234 @@
    <?php
    /**
    * Implements threading in PHP
    *
    * @package <none>
    * @version 1.0.0 - stable
    * @author Tudor Barbu <[email protected]>
    * @copyright MIT
    */
    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;

    /**
    * hodls exit code after child die
    */
    private $exitCode = -1;

    /**
    * checks if threading is supported by the current
    * PHP configuration
    *
    * @return boolean
    */
    public static function available() {
    $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
    */
    public function __construct( $_runnable = null ) {
    if( $_runnable !== null ) {
    $this->setRunnable( $_runnable );
    }
    }

    /**
    * sets the callback
    *
    * @param callback $_runnable
    * @return callback
    */
    public function setRunnable( $_runnable ) {
    if( self::runnableOk( $_runnable ) ) {
    $this->runnable = $_runnable;
    }
    else {
    throw new Exception( $this->getError( Thread::FUNCTION_NOT_CALLABLE ), Thread::FUNCTION_NOT_CALLABLE );
    }
    }

    /**
    * gets the callback
    *
    * @return callback
    */
    public function getRunnable() {
    return $this->runnable;
    }

    /**
    * checks if the callback is ok (the function/method
    * actually exists and is runnable from the current
    * context)
    *
    * can be called statically
    *
    * @param callback $_runnable
    * @return boolean
    */
    public static function runnableOk( $_runnable ) {
    return ( function_exists( $_runnable ) && 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 );
    if ($pid === 0) { // child is still alive
    return true;
    } else {
    if (pcntl_wifexited($starts)) { // normal exit
    $this->exitCode = pcntl_wexitstatus($status);
    }
    return false;
    }
    }

    /**
    * return exit code of child
    *
    * @return int
    */
    public function getExitCode() {
    return $this->exitCode;
    }

    /**
    * starts the thread, all the parameters are
    * passed to the callback function
    *
    * @return void
    */
    public function start() {
    $pid = @ pcntl_fork();
    if( $pid == -1 ) {
    throw new Exception( $this->getError( Thread::COULD_NOT_FORK ), Thread::COULD_NOT_FORK );
    }
    if( $pid ) {
    // parent
    $this->pid = $pid;
    }
    else {
    // child
    pcntl_signal( SIGTERM, array( $this, 'signalHandler' ) );
    $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/SIGTERM
    * @param boolean $_wait
    */
    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();
    *
    * @return boolean
    */
    public function kill( $_signal = SIGKILL, $_wait = false ) {
    return $this->stop( $_signal, $_wait );
    }

    /**
    * gets the error's message based on
    * its id
    *
    * @param integer $_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
    */
    protected function signalHandler( $_signal ) {
    switch( $_signal ) {
    case SIGTERM:
    exit( 0 );
    break;
    }
    }
    }

    // EOF