Examples of synchronized usage in PHP pthreads V3

Time:2021-6-18

This paper introduces the usage of synchronized in PHP pthreads v3. The details are as follows:

Synchronization is used because if multiple threads operate on the same resource, there will be confusion.

For example, two threads add one to a variable. Before the first thread arrives and rewrites the data, the second thread operates on the variable. The final result of the variable is unknown. At this time, synchronization is needed to control it.

Examples are as follows:

<?php
class Count extends Thread
{
  public $cnt = 0;

  public function run()
  {
    $this->add();
  }

  public function add()
  {
    //Add 1 to members
    for ($i = 0; $i < 100000; $i++) {
      ++$this->cnt;
    }
  }
}

$c = new Count();
//When start() is called, the add() method is called in the thread run()
$c->start();
//If we call the add () method again, there will be two for loops to operate on $CNT
$c->add();
//Add the created thread to the main thread and let the main thread wait for the end of the sub thread
$c->join();

//Here the output is uncertain
var_dump($c->cnt);

After multiple runs, the value of $CNT is uncertain. As shown in the figure below:

In pthreads V2, we can use mutex, but it has been deleted in V3, so we can simply put the plus 1 operation into synchronized for synchronization. The code is as follows:

<?php
class Count extends Thread
{
  public $cnt = 0;

  public function run()
  {
    $this->add();
  }

  public function add()
  {
    $this->synchronized(function () {
      //Add 1 to members
      for ($i = 0; $i < 100000; $i++) {
        ++$this->cnt;
      }
    });
  }
}

$c = new Count();
//When start() is called, the add() method is called in the thread run()
$c->start();
//If we call the add () method again, there will be two for loops to operate on $CNT
$c->add();
//Add the created thread to the main thread and let the main thread wait for the end of the sub thread
$c->join();

//It's going to output 200000 all the time
var_dump($c->cnt);

The results are as follows

Of course, we can also control synchronization through notify() and wait(). The code is as follows:

<?php
class Task extends Thread
{
  public $flag = 1;

  public function run()
  {
    $this->synchronized(function () {
      //Waiting if Id is not 1
      if ($this->flag !== 1) {
        $this->wait();
      }

      for ($i = 1; $i <= 10; $i++) {

        echo "flag : {$this->flag} i : {$i} \n";

        if ($this->flag === 1) {
          //Set logo
          $this->flag = 2;
          //Send wake-up notification, and then let the current thread wait
          //Note that the order of notify() and wait() should not be wrong, otherwise it will block all the time
          $this->notify();
          $this->wait();
        }
      }

      //We call notify () again here
      //Because when the last output flag: 2 I: 20, the I of the current thread has become 11, jumping out of the for loop,
      //But the other thread has been blocked in wait (), and the program can't end, so notify () needs to wake up again
      $this->notify();
    });
  }
}

$t = new Task();
$t->start();

$t->synchronized(function ($obj) {
  //Waiting if Id is not 2
  if ($obj->flag !== 2) {
    $obj->wait();
  }

  for ($i = 11; $i <= 20; $i++) {

    echo "flag : {$obj->flag} i : {$i} \n";

    if ($obj->flag === 2) {
      $obj->flag = 1;
      $obj->notify();
      $obj->wait();
    }
  }
}, $t);

//Add the created thread to the main thread and let the main thread wait for the end of the sub thread
$t->join();

The results are as follows

We control two for loops through notify() and wait(), and output the value of variable I back and forth to ensure the order.

Let’s take another example of a complex point. If the shared resources are not synchronized, there will be unpredictable situations. The code is as follows:


<?php
class Task extends Thread
{
  private $name;
  private $file;

  public function __construct($name, $file)
  {
    $this->name = $name;
    $this->file = $file;
  }

  public function run()
  {
    $data = file_get_contents($this->file);
    $data = floatval($data);
    for ($i = 0; $i < 100000; $i++) {
      ++$data;
    }
    file_put_contents($this->file, $data);
    echo "task : {$this->name} data : {$data} \n";
  }
}

$tasks = [];
$file = './test.log';

for ($i = 0; $i < 100; $i++) {
  $tasks[$i] = new Task($i, $file);
  $tasks[$i]->start();
}

for ($i = 0; $i < 100; $i++) {
  $tasks[$i]->join();
}

We open 100 threads to read and write the file test. Log. Ideally, the data in test. Log should be increased by 10000000 each time. Now the computer configuration is better, you can run a few more times can see the effect.

Obviously, the final data seems to be less than 200000. If we read and write the test.log file under multithreading, and we do not lock it, it is obvious that there will be data confusion.

Now let’s modify the code as follows:

<?php
class File extends Thread
{
  private $file;

  public function __construct($file)
  {
    $this->file = $file;
  }

  public function inc()
  {
    //When 100 task threads call the Inc method, synchronized can ensure that the code in the block is synchronized
    //Note, note, don't write the Inc method into the task, that is ineffective, because each task thread is an independent space, and they can't synchronize by adjusting their respective Inc methods
    //A common practice is to write a thread class for the resources we want to synchronize, provide methods to operate these resources, and add synchronized in the method
    return $this->synchronized(function () {
      $data = file_get_contents($this->file);
      $data = floatval($data);
      for ($i = 0; $i < 100000; $i++) {
        ++$data;
      }
      file_put_contents($this->file, $data);
      return $data;
    });
  }
}

class Task extends Thread
{
  private $name;
  private $file;

  public function __construct($name, $file)
  {
    $this->name = $name;
    $this->file = $file;
  }

  public function run()
  {
    $data = $this->file->inc();
    echo "task : {$this->name} data : {$data} \n";
  }
}

$tasks = [];
$file = new File('./test.log');

for ($i = 0; $i < 100; $i++) {
  $tasks[$i] = new Task($i, $file);
  $tasks[$i]->start();
}

for ($i = 0; $i < 100; $i++) {
  $tasks[$i]->join();
}

The result is shown in the figure below. Of course, for the sake of safety, we can try to run it several times. Here is the result of 25 times of running

For more information about PHP, readers interested in this site can see the following topics: summary of PHP process and thread operation skills, summary of PHP network programming skills, introductory course of PHP basic syntax, complete collection of PHP array operation skills, summary of PHP string usage “PHP + MySQL database operation tutorial” and “PHP common database operation skills summary”

I hope this article is helpful for PHP programming.