I have small code that demonstrate how to perform a race condition in multithreads PHP.
The idea is I and my friend is sharing the pot for cooking. if the pot already have ingredient, so the pot can not cook.
class Pot:
class Pot
{
public $id;
function __construct()
{
$this->id = rand();
}
public $ingredient;
public function cook($ingredient, $who, $time){
if ($this->ingredient==null){
$this->ingredient = $ingredient;
print "pot".$this->id.'/'.$who." cooking ".$this->ingredient. " time spent: ".$time." \n";
sleep($time);
print "pot".$this->id.'/'.$who." had flush ingredient \n";
$this->ingredient = null;
}else{
throw new Exception("Pot still cook ".$this->ingredient);
}
}
}
class Friend:
class Friend extends Thread
{
/**
* @var Pot
*/
protected $pot;
function run() {
Cocking::cleanVegetable("Friend");
print "Friend will cook: \n";
$this->pot->cook("vegetable", 'Friend',4);
Cocking::digVegetable("Friend");
}
public function __construct($pot)
{
$this->pot = $pot;
}
}
class My:
class My
{
/**
* @var Pot
*/
private $pot;
public function doMyJob(){
Cocking::cleanRice("I");
print "I will cook: \n";
$this->pot->cook("rice", "I",10);
Cocking::digRice("I");
}
public function playGame(Friend $friend){
print "play with friend \n";
}
public function __construct($pot)
{
$this->pot = $pot;
}
}
class Coocking:
<?php
class Cocking
{
static function cleanRice($who){
print $who." is cleaning rice \n";
}
static function cleanVegetable($who){
print $who."is cleaning vegetable \n";
}
static function digRice($who){
print $who." is digging rice \n";
}
static function digVegetable($who){
print $who." is digging vegetable \n";
}
}
running script:
require_once "Friend.php";
require_once "My.php";
require_once "Cocking.php";
require_once "Pot.php";
$pot = new Pot();
$friend = new Friend($pot);
$my = new My($pot);
$friend->start();
$my->doMyJob();
$friend->join();
$my->playGame($friend);
that is so wreid that the output never throw exception? that i assume always happen.
root@e03ed8b56f21:/app/RealLive# php index.php
Friendis cleaning vegetable
I is cleaning rice
Friend will cook:
I will cook:
pot926057642/I cooking rice time spent: 10
pot926057642/Friend cooking vegetable time spent: 4
pot926057642/Friend had flush ingredient
Friend is digging vegetable
pot926057642/I had flush ingredient
I is digging rice
play with friend
the Pot had used by me, but my friend still can use it to cook vegetable. that so freak?
i expect the result would be:
Friend will cook:
I will cook:
pot926057642/I cooking rice time spent: 10
PHP Fatal error: Uncaught Exception: Pot still cook rice in /app/RealLive/Pot.php:23
Stack trace:
#0 /app/RealLive/My.php(14): Pot->cook('rice', 'I', 10)
#1 /app/RealLive/index.php(12): My->doMyJob()
#2 {main}
thrown in /app/RealLive/Pot.php on line 23
ps: my env is
PHP 7.0.10 (cli) (built: Apr 30 2019 21:14:24) ( ZTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
Many thanks from your comment.
Your assumption seems to be that your
ifcondition followed by an immediate member assign always needs to run in one go. However, it is entirely possible thatFriendruns this line of code in the thread:... and concludes to go ahead, but before it reaches the next line that assigns
$this->ingredient, execution switches back toMy/main thread, where it also gets to this line:And since
Friendhas passed theifbut not proceeded to actually assigned the ingredient yet,Mycan now also pass inside. Whatever runs next doesn't matter, you now got both threads accessing the pot cooking at the same time.Additional correction/note: it seems like that the example also doesn't work since
$this->ingredientisn't aVolatile. However, that would still make it prone to above race condition and hence still a bad idea.How to do it properly: You really need to use a mutex or synchronized section for proper synchronization. Also, never ever assume threads can't switch in the middle of anywhere, including any two lines like an
iffollowed by a variable assign that was meant as a pair.Here is the PHP documentation on the synchronized section: https://www.php.net/manual/en/threaded.synchronized.php