PHP:共享公共对象的线程代理

时间:2015-11-22 15:10:43

标签: php object pthreads shared synchronize

我正在使用非线程PHP运行CGI程序,这是人工生命和进化的一个小练习。生物体具有基因组和解释器,这使得它们执行某些操作,包括在共享的世界地图中移动和相互交互。目前我通过使用通过MySQL数据库交互的多个PHP进程来保持较差的线程外观,但我想重写代码,以便它使用pthreads在单个线程中连续运行,不一定使用数据库(尽管我' d可能想保留报告)。

我一直在浏览github上的问题和解答问题和代码示例,但是没有找到任何东西 - 据我所知 - 解决了我想要的问题。因为我不是一个天才的OOP编码器,而且我是编写线程代码的新手,特别是在PHP中,我的问题会相当广泛。

我试图通过编写显示我正在尝试做的代码来缩小我的问题的范围,但它可能仍然过于宽泛。我对如何进一步缩小范围表示赞赏。

我对以下代码的疑问:

  • 如何让Organism对共享的World对象进行操作,以便将World对象中的更改传递给所有线程,避免冲突并保持连贯性?
  • 鉴于人口规模最终是可变的,有没有办法让世界对象的有机体部分(即$ world->有机体), 让World能够创建新的Organities,如下(错误)代码所示?
  • 鉴于我最终想要创建数百个生物群,你是否有任何关于限制活动线程数量(即限制内存/ CPU使用率)的指针,同时仍保持一致性?

下面的代码(当然没有运行,但是)说明了我想要实现的目标:

columns: [
   { data: "CategoryId" }, 
   { data: "Name" },
   { data: "Code" },
   { data: "TypeName" },
   { data: "IsAbstract" },
   { data: "DefSrcOrganisationName" },
   { data: null,
     sortable: false,
     render: function(data, type, row) {
         return '<a href="kugi/edit_usulan_kategori/'+row.CategoryId+'">'+
                '<button class="btn btn-success btn-xs">Edit</button></a>'
     }
   }      
]

1 个答案:

答案 0 :(得分:2)

我将回答pthreads v3,PHP7。

请不要将pthreads v2用于新项目,v3要优越得多。

  

如何让Organism对共享的World对象进行操作,以便将World对象中的更改传递给所有线程,避免冲突并保持连贯性?

以下代码创建了一组操纵共享对象的线程:

<?php
class Test extends Thread {

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

    public function run() {
        $this->shared[] = $this->getThreadId();
    }
}

$shared = new Threaded();
$tests = [];
for ($i = 0; $i<20; $i++) {
    $tests[$i] = 
        new Test($shared);
    $tests[$i]->start();
}

foreach ($tests as $test)
    $test->join();

var_dump($shared);
?>

会产生类似于:

的东西
object(Threaded)#1 (20) {
  [0]=>
  int(140322714146560)
  [1]=>
  int(140322703144704)
  [2]=>
  int(140322621355776)
  [3]=>
  int(140322612963072)
  [4]=>
  int(140322604570368)
  [5]=>
  int(140322596177664)
  [6]=>
  int(140322587784960)
  [7]=>
  int(140322579392256)
  [8]=>
  int(140322570999552)
  [9]=>
  int(140322487138048)
  [10]=>
  int(140322478745344)
  [11]=>
  int(140322470352640)
  [12]=>
  int(140322461959936)
  [13]=>
  int(140322453567232)
  [14]=>
  int(140322445174528)
  [15]=>
  int(140322436781824)
  [16]=>
  int(140322428389120)
  [17]=>
  int(140322419996416)
  [18]=>
  int(140322411603712)
  [19]=>
  int(140322403211008)
}

元素数量一致并非偶然:

$this->shared[] = $this->getThreadId();

执行此操作时,pthreads会为您提供安全

任何引用Thread Threaded对象的$shared都可以操纵它。

一致性和安全性是两回事,请考虑以下代码:

<?php
class Test extends Thread {

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

    public function run() {
        if (!isset($this->shared[0])) {
            $this->shared[0] = $this->getThreadId();
        }
    }

    private $shared;
}

$shared = new Threaded();
$tests  = [];

for ($i = 0; $i < 16; $i++) {
    $tests[$i] = 
        new Test($shared);
    $tests[$i]->start();
}

foreach ($tests as $test)
    $test->join();
?>

您可能只希望有一个Thread走这条路:

$this->shared[0] = $this->getThreadId();

但是,不能保证这一点。由于在调用isset和上述语句之间没有锁定,因此许多线程可以自由地同时传播路径。

使用以下代码替换Test的run方法将确保一致性:

    public function run() {
        $this->shared->synchronized(function(){
            if (!isset($this->shared[0])) {
                $this->shared[0] = $this->getThreadId();
            }
        });
    }
  

鉴于人口规模最终是可变的,有没有办法将世界对象的生物体部分(即$ world-&gt;有机体)引用,并让世界能够创造新的有机体,如如下所示(有缺陷的)代码?

这听起来违反了SOLID的基本原则。无论如何,追逐都不是一件好事。

听起来有机体应属于主要过程,因此需要在那里构建并传递到ThreadWorker或其他任何内容。

他们需要属于主要流程,因为它们可能会以多个Thread结尾。

  

鉴于我最终想要创建数百个生物群,你是否有任何关于限制活动线程数量(即限制内存/ CPU使用率)的指针,同时仍保持一致性?

使用pthreads提供的Pool实现。

以下是一些代码:

<?php
class Organisms extends Volatile {}

class World extends Threaded {

    public function __construct(Organisms $organisms, Volatile $grid) {
        $this->organisms = $organisms;
        $this->grid = $grid;
    }

    public function populate($organism, int $x, int $y) : bool {
        $reference = $this->getGridReference($x, $y);

        return $this->grid->synchronized(function() use($organism, $reference) {
            if (isset($this->grid[$reference]))
                return false;
            return (bool) $this->grid[$reference] = $organism;
        });
    }

    private function getGridReference(int $x, int $y) {
        return sprintf("%dx%d", $x, $y);
    }

    public function getOrganisms() { return $this->organisms; }

    private $organisms;
}

class Organism extends Threaded {

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

    public function setPosition(int $x, int $y) {
        $this->x = $x;
        $this->y = $y;
    }

    public function getWorld() { return $this->world; }

    private $world;
    private $x = -1;
    private $y = -1;
}

class OrganismPopulateTask extends Threaded {

    public function __construct(World $world, Organism $organism, int $x, int $y) {
        $this->world = $world;
        $this->organism = $organism;
        $this->x = $x;
        $this->y = $y;
    }

    public function run() {
        if ($this->world->populate(
            (object) $this->organism, $this->x, $this->y)) {
                $this->organism->setPosition($this->x, $this->y);
        }
    }

    private $world;
    private $organism;
    private $x;
    private $y;
}

$organisms = new Organisms();
$grid = new Volatile();
$world = new World($organisms, $grid);
$pool = new Pool(16);

$organisms[] = new Organism($world);
$organisms[] = new Organism($world);

$pool
    ->submit(new OrganismPopulateTask($world, $organisms[0], 10, 10));
$pool   
    ->submit(new OrganismPopulateTask($world, $organisms[1], 10, 10));

$pool->shutdown();

var_dump($world);
?>

将屈服:

object(World)#3 (2) {
  ["organisms"]=>
  object(Organisms)#1 (2) {
    [0]=>
    object(Organism)#5 (3) {
      ["world"]=>
      *RECURSION*
      ["x"]=>
      int(10)
      ["y"]=>
      int(10)
    }
    [1]=>
    object(Organism)#6 (3) {
      ["world"]=>
      *RECURSION*
      ["x"]=>
      int(-1)
      ["y"]=>
      int(-1)
    }
  }
  ["grid"]=>
  object(Volatile)#2 (1) {
    ["10x10"]=>
    object(Organism)#5 (3) {
      ["world"]=>
      *RECURSION*
      ["x"]=>
      int(10)
      ["y"]=>
      int(10)
    }
  }
}

注意:这使用了v3.1.2

中包含的功能

大多数应该是自我解释的,显式强制转换是为了避免因尝试连接已经消失的对象而导致的异常。

要注意的主要是每个&#34;动作&#34;被视为&#34;任务&#34;并提交给Pool