php pthreads locking variable

I'm currently getting familiar with php threads. Found worker and collectable classess pretty intersting and convenient. But I can't find out how to lock variables for changing

class job extends Collectable {
  public static $count = 0;
  public $url;
  private $file = "outfile.txt";
  public function __construct($url){
    // init some properties
    $this->url = $url;

  }
  public function run(){
    // do some work
    //$this->val = $this->val . file_get_contents('http://www.example.com/', null, null, 3, 20);
    //echo $this->url;
    $data = explode("|", $this->url);
    $vars = GetServerVars($data[0], $data[1]);
    $this->lock();
    self::$count++;
    $this->unlock();
    echo "current_count: ".self::$count."\n";
    $this->setGarbage();
  }
}

For some reason this doesn't work and I get number 1,2,3,4 for few times in a row. So self::$count doesn't increment successively. Why this could happen? And what is the way to correct lock variables in pthreads? Thanks!

Upvotes: 1

Views: 1382

Answers (1)

Joe Watkins
Joe Watkins

Reputation: 17158

Static variables are thread local, so you cannot use a static variable as a shared counter.

The following code will create a stupid number of threads (100), each thread accepts a Threaded result object and an int $value.

If $value is an even number we consider it a success, otherwise it is a failure; we execute an appropriate function to increment a shared counter safely.

<?php
class Results extends Threaded {

    public function addError() {
        return $this->synchronized(function(){
            return $this->error++;
        });
    }

    public function addSuccess() {
        return $this->synchronized(function(){
            return $this->success++;
        });
    }

    private $error;
    private $success;
}

class Process extends Thread {

    public function __construct(Results $results, int $value) {
        $this->results = $results;
        $this->value = $value;
    }

    public function run() {
        if ($this->value % 2 == 0)
            $this->results->addSuccess();
        else $this->results->addError();
    }

    private $results;
    private $value;
}

$results   = new Results();
$processes = [];
$process   = 0;

do {
    $processes[$process] = 
        new Process($results, mt_rand(1, 100));
    $processes[$process]->start();
} while (++$process < 100);

foreach ($processes as $process)
    $process->join();

var_dump($results);
?>

Note: this is PHP 7 + pthreads v3 code ... don't use v2 for new projects

Calling synchronized ensures that no other context can enter a synchronized block for the same object while the calling context is, this ensures safety for the operations provided in the synchronized block:

object(Results)#1 (2) {
  ["error"]=>
  int(49)
  ["success"]=>
  int(51)
}

The inquisitive programmer will probably now proceed to removing the synchronized blocks, and, much to their surprise will find that the output is the same.

The reason for this is because operations on objects members are atomic - the increment and decrement instructions can be assumed to be atomic, in other words.

It's too hard for you to guess which operations are going to be atomic, so don't.

Assumptions are horrible; as soon as the code in the synchronized block is more complex than single instructions your code is open to race conditions. The sensible thing to do is set a standard that says if you require atomicity for any number of statements they should be executed in a synchronized block.

Upvotes: 5

Related Questions