I'm running a CGI program in non-threaded PHP, a little exercise in artificial life and evolution. Organisms have a genome and an interpreter, which causes them to perform certain operations, including moving about and interacting with one another in a shared World map. Currently I'm maintaining a poor semblance of threading by using multiple PHP processes interacting via a MySQL database, but I want to rewrite the code so that it uses pthreads to run continuously in a single thread, not necessarily using a database (although I'd probably want to keep it for reporting).
I've been browsing through asked and answered questions and code examples on github, but haven't managed to find anything that - as far as I can tell - addresses the thing I want. As I am not exactly a genius OOP coder and am entirely new to writing threaded code, especially in PHP, my questions will be fairly broad.
I've tried to narrow down the scope of my questions a bit by writing code showing what I'm trying to do, but it may still be too broad. I'd appreciate any advice on how to narrow it down further.
My questions about the code below:
- How do I get an Organism to act on a shared World object so that the changes in the World object are communicated to all threads, avoiding conflicts and maintaining coherence?
- Given that the population size is ultimately variable, is there a way to make the references to the Organisms part of the World object (ie. $world->organisms), and have World be able to create new Organisms, as shown in below (faulty) code?
- Given that I ultimately want to create a population of hundreds of Organisms, do you have any pointers on limiting the number of active threads (ie. limiting memory/cpu usage) while still maintaining consistency?
Below code (doesn't run, of course, but) illustrates what I am seeking to achieve:
/*
* Shared object containing a map of the world and
* methods for getting/setting coordinates
*/
class World extends Thread
{
public $map;
public $sx;
public $sy;
public $organisms;
// set all map coords to 0
public function __construct($sx, $sy, $p)
{
$map = array();
for( $i = 0; $i < $sx; $i++ )
{
$map[$i] = array();
for( $j = 0; $j < $sy; $j++ )
{
$map[$i][$j] = 0;
}
}
$this->map = $map;
$this->sx = $sx;
$this->sy = $sy;
// create and start organism threads
$this->organisms = array();
for( $i = 0; $i < $p; $i++ )
{
// this won't work because threaded objects created
// within a thread scope that have no external references
// are destroyed in the constructor
$this->makeOrganism($i+1, $i+1, $i+1);
}
}
// produces a new organism, adds to world population
public function makeOrganism($x, $y, $value)
{
if( $x < 1 || $x > $this->sx ) return false;
if( $y < 1 || $y > $this->sy ) return false;
if( $this->getMap($x, $y) != 0 ) return false;
echo "creating new organism $value\n";
$organisms = $this->organisms;
// doesn't work because the world data is decoupled in the new thread
$organisms[] = new Organism($this, $x, $y, $value);
$this->organisms = $organisms;
return true;
}
// assumes valid coords
public function setMap($x, $y, $value)
{
return $this->map[$x-1][$y-1] = $value;
}
// assumes valid coords
public function getMap($x, $y)
{
return $this->map[$x-1][$y-1];
}
public function getSX()
{
return $this->sx;
}
public function getSY()
{
return $this->sy;
}
public function run()
{
for( $i = 0; $i < count($this->organisms); $i++ )
{
echo "starting organism ", $this->value, "\n";
$this->organisms[$i]->start();
}
}
}
/*
* Autonomously running agent accessing shared World map
*/
class Organism extends Thread
{
public $value;
public $world;
public $x;
public $y;
public function __construct(World $world, $x, $y, $value)
{
$this->world = $world;
$this->value = $value;
$this->x = $x;
$this->y = $y;
// assume coordinates are empty
$this->world->setMap($x, $y, $value);
}
// try to move organism by $dx, $dy
public function move($dx, $dy)
{
$x = $this->x + $dx;
$y = $this->y + $dy;
if( $x < 1 || $x > $this->world->getSX() ) return false;
if( $y < 1 || $y > $this->world->getSY() ) return false;
if( $this->world->getMap($x, $y) != 0 ) return false;
$this->world->setMap($x, $y, $this->value);
$this->world->setMap($this->x, $this->y, 0);
$this->x = $x;
$this->y = $y;
return true;
}
public function getValue()
{
return $this->value;
}
public function run()
{
// infinite loop; organisms move randomly about until they die
while( true )
{
echo "running organism ", $this->getValue(), "\n";
// this should operate on the shared object World,
// maintaining consistency and avoiding conflicts between threads
$dx = rand(-1, 1);
$dy = rand(-1, 1);
$this->move($dx, $dy);
// occasionally add an organism to the population by cloning this one
if( rand(0, 100) > 95 )
{
$this->world->makeOrganism($this->x+1, $this->y+1, $this->value+100);
}
// wait random interval, organisms are
// not expected to move all at the same time
$this->wait(1000 + rand(500, 1500));
}
}
}
// initialize shared object
$world = new World(50, 50, 50);
$world->start();
$world->join();