4

I'm writing a script that needs to execute concurrent tasks in PHP.

I ran a little test and ran into strange result. I'm using pcntl_fork to generate a child. The parent process does nothing but wait for the child to complete.

I'm generating 5 children, each child runs a function that generates a random number (of seconds) and sleeps for that long. For some reason - all children generate the same number.

Here's a code example:

private $_child_count = 0;

private function _fork_and_exec($func)
{
    $cid = ++$this->_child_count;
    $pid = pcntl_fork();
    if ($pid){  // parent
        return $pid;
    } else {    // child
        $func($cid);
        //pcntl_waitpid(-1, $status);
        exit;
    }
}
public function parallel_test()
{
    $func = function($id){
        echo 'child ' . $id . ' starts'."\n";
        $wait_time = mt_rand(1,4);
        echo 'sleeping for '.$wait_time."\n";
        sleep($wait_time);
        echo 'child ' . $id . ' ends'."\n";
    };
    $children = [];
    for ($i=0; $i<5; $i++){
        $children[] = $this->_fork_and_exec($func) ."\n";
    }
    pcntl_wait($status);
    echo 'done' ."\n";
    exit;
}

Example output:

child 1 starts
sleeping for 1
child 2 starts
sleeping for 1
child 3 starts
sleeping for 1
child 4 starts
sleeping for 1
child 5 starts
sleeping for 1
child 1 ends
child 2 ends
child 3 ends
child 4 ends
child 5 ends
done

thanks in advance

galchen
  • 5,252
  • 3
  • 29
  • 43
  • Different language, same idea: http://stackoverflow.com/questions/13437023/random-number-between-forked-processes , http://stackoverflow.com/questions/8623131/c-rand-not-so-random-after-fork –  Feb 14 '13 at 16:21

2 Answers2

6

That is because all children start with the same state (fork() duplicates the code and data segments). And since rand and mt_rand are pseudorandom generators, they will all generate the same sequence.

You will have to re-initialize the random generator, for example with the process/thread ID or read a few bytes from /dev/urandom.

JvO
  • 3,036
  • 2
  • 17
  • 32
  • 2
    To build on this answer a little bit, use something like `mt_srand` with the thread ID to re-seed the random number generator – Colin M Feb 14 '13 at 16:18
1

I really think you should look at pthreads which provides multi-threading that is compatible with PHP based on Posix Threads.

Simple as

class AsyncOperation extends Thread {
    public function __construct($arg) {
        $this->arg = $arg;
    }
    public function run() {
        if ($this->arg) {
            echo 'child ' . $this->arg . ' starts' . "\n";
            $wait_time = mt_rand(1, 4);
            echo 'sleeping for ' . $wait_time . "\n";
            sleep($wait_time);
            echo 'child ' . $this->arg . ' ends' . "\n";
        }
    }
}
$t = microtime(true);
$g = array();
foreach(range("A","D") as $i) {
    $g[] = new AsyncOperation($i);
}
foreach ( $g as $t ) {
    $t->start();
}

Output

child B starts
sleeping for 3
child B ends
child C starts
sleeping for 3
child C ends
child A starts
sleeping for 4
child A ends
child D starts
sleeping for 4
child D ends
Baba
  • 94,024
  • 28
  • 166
  • 217
  • ok, better organized code, OO, synchronization is already implemented, I'm convinced. Thanks – galchen Feb 15 '13 at 06:21
  • you don't need to `fock` anything this is `100% POSIX Threads` in `PHP` – Baba Feb 15 '13 at 09:12
  • why is it better than forking? Problem is i need thread-safe enabled and I'm running php-fpm with nginx so it's disabled atm since it wasn't necessary, so I'll need to reinstall php. before that i need to check if it has performance issues under the current setup – galchen Feb 15 '13 at 10:37
  • forking creates new process which has a fair amount of overhead though it provides greater isolation – Baba Feb 15 '13 at 11:44